@@ -1337,6 +1337,7 @@ void DefaultActorImpl::enqueue(Job *job, JobPriority priority) {
13371337 bool distributedActorIsRemote = swift_distributed_actor_is_remote (this );
13381338
13391339 auto oldState = _status ().load (std::memory_order_relaxed);
1340+ SwiftDefensiveRetainRAII thisRetainHelper{this };
13401341 while (true ) {
13411342 auto newState = oldState;
13421343
@@ -1366,15 +1367,29 @@ void DefaultActorImpl::enqueue(Job *job, JobPriority priority) {
13661367 if (needsScheduling || needsStealer)
13671368 taskExecutor = TaskExecutorRef::fromTaskExecutorPreference (job);
13681369
1370+ // In some cases (we aren't scheduling the actor and priorities don't
1371+ // match) then we need to access `this` after the enqueue. But the enqueue
1372+ // can cause the job to run and release `this`, so we need to retain `this`
1373+ // in those cases. The conditional here matches the conditions where we can
1374+ // get to the code below that uses `this`.
1375+ bool willSchedule = !oldState.isScheduled () && newState.isScheduled ();
1376+ bool priorityMismatch = oldState.getMaxPriority () != newState.getMaxPriority ();
1377+ if (!willSchedule && priorityMismatch)
1378+ thisRetainHelper.defensiveRetain ();
1379+
13691380 // This needs to be a store release so that we also publish the contents of
13701381 // the new Job we are adding to the atomic job queue. Pairs with consume
13711382 // in drainOne.
13721383 if (_status ().compare_exchange_weak (oldState, newState,
13731384 /* success */ std::memory_order_release,
13741385 /* failure */ std::memory_order_relaxed)) {
13751386 // NOTE: `job` is off limits after this point, as another thread might run
1376- // and destroy it now that it's enqueued.
1387+ // and destroy it now that it's enqueued. `this` is only accessible if
1388+ // `retainedThis` is true.
1389+ job = nullptr ; // Ensure we can't use it accidentally.
13771390
1391+ // NOTE: only the pointer value of `this` is used here, so this one
1392+ // doesn't need a retain.
13781393 traceActorStateTransition (this , oldState, newState, distributedActorIsRemote);
13791394
13801395 if (!oldState.isScheduled () && newState.isScheduled ()) {
@@ -1385,6 +1400,9 @@ void DefaultActorImpl::enqueue(Job *job, JobPriority priority) {
13851400
13861401#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
13871402 if (oldState.getMaxPriority () != newState.getMaxPriority ()) {
1403+ // We still need `this`, assert that we did a defensive retain.
1404+ assert (thisRetainHelper.isRetained ());
1405+
13881406 if (newState.isRunning ()) {
13891407 // Actor is running on a thread, escalate the thread running it
13901408 SWIFT_TASK_DEBUG_LOG (" [Override] Escalating actor %p which is running on %#x to %#x priority" , this , newState.currentDrainer (), priority);
@@ -1394,11 +1412,12 @@ void DefaultActorImpl::enqueue(Job *job, JobPriority priority) {
13941412 } else {
13951413 // We are scheduling a stealer for an actor due to priority override.
13961414 // This extra processing job has a reference on the actor. See
1397- // ownership rule (2).
1415+ // ownership rule (2). That means that we need to retain `this`, which
1416+ // we'll take from the retain helper.
1417+ thisRetainHelper.takeRetain ();
13981418 SWIFT_TASK_DEBUG_LOG (
13991419 " [Override] Scheduling a stealer for actor %p at %#x priority" ,
14001420 this , newState.getMaxPriority ());
1401- swift_retain (this );
14021421
14031422 scheduleActorProcessJob (newState.getMaxPriority (), taskExecutor);
14041423 }
0 commit comments