@@ -1337,6 +1337,7 @@ void DefaultActorImpl::enqueue(Job *job, JobPriority priority) {
1337
1337
bool distributedActorIsRemote = swift_distributed_actor_is_remote (this );
1338
1338
1339
1339
auto oldState = _status ().load (std::memory_order_relaxed);
1340
+ SwiftDefensiveRetainRAII thisRetainHelper{this };
1340
1341
while (true ) {
1341
1342
auto newState = oldState;
1342
1343
@@ -1366,15 +1367,29 @@ void DefaultActorImpl::enqueue(Job *job, JobPriority priority) {
1366
1367
if (needsScheduling || needsStealer)
1367
1368
taskExecutor = TaskExecutorRef::fromTaskExecutorPreference (job);
1368
1369
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
+
1369
1380
// This needs to be a store release so that we also publish the contents of
1370
1381
// the new Job we are adding to the atomic job queue. Pairs with consume
1371
1382
// in drainOne.
1372
1383
if (_status ().compare_exchange_weak (oldState, newState,
1373
1384
/* success */ std::memory_order_release,
1374
1385
/* failure */ std::memory_order_relaxed)) {
1375
1386
// 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.
1377
1390
1391
+ // NOTE: only the pointer value of `this` is used here, so this one
1392
+ // doesn't need a retain.
1378
1393
traceActorStateTransition (this , oldState, newState, distributedActorIsRemote);
1379
1394
1380
1395
if (!oldState.isScheduled () && newState.isScheduled ()) {
@@ -1385,6 +1400,9 @@ void DefaultActorImpl::enqueue(Job *job, JobPriority priority) {
1385
1400
1386
1401
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
1387
1402
if (oldState.getMaxPriority () != newState.getMaxPriority ()) {
1403
+ // We still need `this`, assert that we did a defensive retain.
1404
+ assert (thisRetainHelper.isRetained ());
1405
+
1388
1406
if (newState.isRunning ()) {
1389
1407
// Actor is running on a thread, escalate the thread running it
1390
1408
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) {
1394
1412
} else {
1395
1413
// We are scheduling a stealer for an actor due to priority override.
1396
1414
// 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 ();
1398
1418
SWIFT_TASK_DEBUG_LOG (
1399
1419
" [Override] Scheduling a stealer for actor %p at %#x priority" ,
1400
1420
this , newState.getMaxPriority ());
1401
- swift_retain (this );
1402
1421
1403
1422
scheduleActorProcessJob (newState.getMaxPriority (), taskExecutor);
1404
1423
}
0 commit comments