Skip to content

Commit 3f0d61c

Browse files
committed
[Concurrency runtime] Don't read from the actor after transitioning state
Once we have transitioned the actor into a new state, we report the state change as a trace event so it can be noted by tools (e.g., Instruments). However, the act of transitioning to a new state can mean that there is an opportunity for another thread to deallocate the actor. This means that the tracing call cannot depend on dereferencing the actor pointer. A refactoring a few months ago to move the bit that indicates when a distributed actor is remote from inside the atomic actor state out to a separate field (because it's constant for a given actor instance), which introduced a dereference of the actor instance in forming the tracing call. This introduced a narrow window in which a race condition could occur: the actor transitions to an idle state, and is then deallocate before the trace event for the actor transition occurs, leading to a use-after-free. Fetch this bit of information earlier in the process, before any state changes and when we know the actor is still allocated, and pass it through to the tracing code. Fixes rdar://108497870.
1 parent 8b4fe6b commit 3f0d61c

File tree

1 file changed

+14
-9
lines changed

1 file changed

+14
-9
lines changed

stdlib/public/Concurrency/Actor.cpp

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -794,7 +794,7 @@ class alignas(sizeof(void *) * 2) ActiveActorStatus {
794794
}
795795
#endif
796796

797-
void traceStateChanged(HeapObject *actor) {
797+
void traceStateChanged(HeapObject *actor, bool distributedActorIsRemote) {
798798
// Convert our state to a consistent raw value. These values currently match
799799
// the enum values, but this explicit conversion provides room for change.
800800
uint8_t traceState = 255;
@@ -814,7 +814,7 @@ class alignas(sizeof(void *) * 2) ActiveActorStatus {
814814
}
815815
concurrency::trace::actor_state_changed(
816816
actor, getFirstJob().getRawJob(), getFirstJob().needsPreprocessing(),
817-
traceState, swift_distributed_actor_is_remote((HeapObject *) actor),
817+
traceState, distributedActorIsRemote,
818818
isMaxPriorityEscalated(), static_cast<uint8_t>(getMaxPriority()));
819819
}
820820
};
@@ -1176,12 +1176,12 @@ static void traceJobQueue(DefaultActorImpl *actor, Job *first) {
11761176
}
11771177

11781178
static SWIFT_ATTRIBUTE_ALWAYS_INLINE void traceActorStateTransition(DefaultActorImpl *actor,
1179-
ActiveActorStatus oldState, ActiveActorStatus newState) {
1179+
ActiveActorStatus oldState, ActiveActorStatus newState, bool distributedActorIsRemote) {
11801180

11811181
SWIFT_TASK_DEBUG_LOG("Actor %p transitioned from %#x to %#x (%s)", actor,
11821182
oldState.getOpaqueFlags(), newState.getOpaqueFlags(),
11831183
__FUNCTION__);
1184-
newState.traceStateChanged(actor);
1184+
newState.traceStateChanged(actor, distributedActorIsRemote);
11851185
}
11861186

11871187
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
@@ -1206,6 +1206,7 @@ void DefaultActorImpl::enqueue(Job *job, JobPriority priority) {
12061206
SWIFT_TASK_DEBUG_LOG("Enqueueing job %p onto actor %p at priority %#zx", job,
12071207
this, priority);
12081208
concurrency::trace::actor_enqueue(this, job);
1209+
bool distributedActorIsRemote = swift_distributed_actor_is_remote(this);
12091210
auto oldState = _status().load(std::memory_order_relaxed);
12101211
while (true) {
12111212
auto newState = oldState;
@@ -1233,7 +1234,7 @@ void DefaultActorImpl::enqueue(Job *job, JobPriority priority) {
12331234
if (_status().compare_exchange_weak(oldState, newState,
12341235
/* success */ std::memory_order_release,
12351236
/* failure */ std::memory_order_relaxed)) {
1236-
traceActorStateTransition(this, oldState, newState);
1237+
traceActorStateTransition(this, oldState, newState, distributedActorIsRemote);
12371238

12381239
if (!oldState.isScheduled() && newState.isScheduled()) {
12391240
// We took responsibility to schedule the actor for the first time. See
@@ -1276,6 +1277,7 @@ void DefaultActorImpl::enqueueStealer(Job *job, JobPriority priority) {
12761277

12771278
SWIFT_TASK_DEBUG_LOG("[Override] Escalating an actor %p due to job that is enqueued being escalated", this);
12781279

1280+
bool distributedActorIsRemote = swift_distributed_actor_is_remote(this);
12791281
auto oldState = _status().load(std::memory_order_relaxed);
12801282
while (true) {
12811283
// Until we figure out how to safely enqueue a stealer and rendevouz with
@@ -1314,7 +1316,7 @@ void DefaultActorImpl::enqueueStealer(Job *job, JobPriority priority) {
13141316
if (_status().compare_exchange_weak(oldState, newState,
13151317
/* success */ std::memory_order_relaxed,
13161318
/* failure */ std::memory_order_relaxed)) {
1317-
traceActorStateTransition(this, oldState, newState);
1319+
traceActorStateTransition(this, oldState, newState, distributedActorIsRemote);
13181320
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
13191321
if (newState.isRunning()) {
13201322
// Actor is running on a thread, escalate the thread running it
@@ -1344,6 +1346,7 @@ Job * DefaultActorImpl::drainOne() {
13441346
SWIFT_TASK_DEBUG_LOG("Draining one job from default actor %p", this);
13451347

13461348
// Pairs with the store release in DefaultActorImpl::enqueue
1349+
bool distributedActorIsRemote = swift_distributed_actor_is_remote(this);
13471350
auto oldState = _status().load(SWIFT_MEMORY_ORDER_CONSUME);
13481351
_swift_tsan_consume(this);
13491352

@@ -1367,7 +1370,7 @@ Job * DefaultActorImpl::drainOne() {
13671370
/* success */ std::memory_order_relaxed,
13681371
/* failure */ std::memory_order_relaxed)) {
13691372
SWIFT_TASK_DEBUG_LOG("Drained first job %p from actor %p", firstJob, this);
1370-
traceActorStateTransition(this, oldState, newState);
1373+
traceActorStateTransition(this, oldState, newState, distributedActorIsRemote);
13711374
concurrency::trace::actor_dequeue(this, firstJob);
13721375
return firstJob;
13731376
}
@@ -1556,6 +1559,7 @@ retry:;
15561559
SWIFT_TASK_DEBUG_LOG("Thread attempting to jump onto %p, as drainer = %d", this, asDrainer);
15571560
#endif
15581561

1562+
bool distributedActorIsRemote = swift_distributed_actor_is_remote(this);
15591563
auto oldState = _status().load(std::memory_order_relaxed);
15601564
while (true) {
15611565

@@ -1612,7 +1616,7 @@ retry:;
16121616
std::memory_order_acquire,
16131617
std::memory_order_relaxed)) {
16141618
_swift_tsan_acquire(this);
1615-
traceActorStateTransition(this, oldState, newState);
1619+
traceActorStateTransition(this, oldState, newState, distributedActorIsRemote);
16161620
return true;
16171621
}
16181622
}
@@ -1635,6 +1639,7 @@ bool DefaultActorImpl::unlock(bool forceUnlock)
16351639
this->drainLock.unlock();
16361640
return true;
16371641
#else
1642+
bool distributedActorIsRemote = swift_distributed_actor_is_remote(this);
16381643
auto oldState = _status().load(std::memory_order_relaxed);
16391644
SWIFT_TASK_DEBUG_LOG("Try unlock-ing actor %p with forceUnlock = %d", this, forceUnlock);
16401645

@@ -1693,7 +1698,7 @@ bool DefaultActorImpl::unlock(bool forceUnlock)
16931698
/* success */ std::memory_order_release,
16941699
/* failure */ std::memory_order_relaxed)) {
16951700
_swift_tsan_release(this);
1696-
traceActorStateTransition(this, oldState, newState);
1701+
traceActorStateTransition(this, oldState, newState, distributedActorIsRemote);
16971702

16981703
if (newState.isScheduled()) {
16991704
// See ownership rule (6) in DefaultActorImpl

0 commit comments

Comments
 (0)