@@ -1359,6 +1359,7 @@ void DefaultActorImpl::enqueue(Job *job, JobPriority priority) {
13591359 bool distributedActorIsRemote = swift_distributed_actor_is_remote (this );
13601360
13611361 auto oldState = _status ().load (std::memory_order_relaxed);
1362+ SwiftDefensiveRetainRAII thisRetainHelper{this };
13621363 while (true ) {
13631364 auto newState = oldState;
13641365
@@ -1388,15 +1389,29 @@ void DefaultActorImpl::enqueue(Job *job, JobPriority priority) {
13881389 if (needsScheduling || needsStealer)
13891390 taskExecutor = TaskExecutorRef::fromTaskExecutorPreference (job);
13901391
1392+ // In some cases (we aren't scheduling the actor and priorities don't
1393+ // match) then we need to access `this` after the enqueue. But the enqueue
1394+ // can cause the job to run and release `this`, so we need to retain `this`
1395+ // in those cases. The conditional here matches the conditions where we can
1396+ // get to the code below that uses `this`.
1397+ bool willSchedule = !oldState.isScheduled () && newState.isScheduled ();
1398+ bool priorityMismatch = oldState.getMaxPriority () != newState.getMaxPriority ();
1399+ if (!willSchedule && priorityMismatch)
1400+ thisRetainHelper.defensiveRetain ();
1401+
13911402 // This needs to be a store release so that we also publish the contents of
13921403 // the new Job we are adding to the atomic job queue. Pairs with consume
13931404 // in drainOne.
13941405 if (_status ().compare_exchange_weak (oldState, newState,
13951406 /* success */ std::memory_order_release,
13961407 /* failure */ std::memory_order_relaxed)) {
13971408 // NOTE: `job` is off limits after this point, as another thread might run
1398- // and destroy it now that it's enqueued.
1409+ // and destroy it now that it's enqueued. `this` is only accessible if
1410+ // `retainedThis` is true.
1411+ job = nullptr ; // Ensure we can't use it accidentally.
13991412
1413+ // NOTE: only the pointer value of `this` is used here, so this one
1414+ // doesn't need a retain.
14001415 traceActorStateTransition (this , oldState, newState, distributedActorIsRemote);
14011416
14021417 if (!oldState.isScheduled () && newState.isScheduled ()) {
@@ -1407,6 +1422,9 @@ void DefaultActorImpl::enqueue(Job *job, JobPriority priority) {
14071422
14081423#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
14091424 if (oldState.getMaxPriority () != newState.getMaxPriority ()) {
1425+ // We still need `this`, assert that we did a defensive retain.
1426+ assert (thisRetainHelper.isRetained ());
1427+
14101428 if (newState.isRunning ()) {
14111429 // Actor is running on a thread, escalate the thread running it
14121430 SWIFT_TASK_DEBUG_LOG (" [Override] Escalating actor %p which is running on %#x to %#x priority" , this , newState.currentDrainer (), priority);
@@ -1416,11 +1434,12 @@ void DefaultActorImpl::enqueue(Job *job, JobPriority priority) {
14161434 } else {
14171435 // We are scheduling a stealer for an actor due to priority override.
14181436 // This extra processing job has a reference on the actor. See
1419- // ownership rule (2).
1437+ // ownership rule (2). That means that we need to retain `this`, which
1438+ // we'll take from the retain helper.
1439+ thisRetainHelper.takeRetain ();
14201440 SWIFT_TASK_DEBUG_LOG (
14211441 " [Override] Scheduling a stealer for actor %p at %#x priority" ,
14221442 this , newState.getMaxPriority ());
1423- swift_retain (this );
14241443
14251444 scheduleActorProcessJob (newState.getMaxPriority (), taskExecutor);
14261445 }
0 commit comments