@@ -1359,6 +1359,7 @@ void DefaultActorImpl::enqueue(Job *job, JobPriority priority) {
1359
1359
bool distributedActorIsRemote = swift_distributed_actor_is_remote (this );
1360
1360
1361
1361
auto oldState = _status ().load (std::memory_order_relaxed);
1362
+ SwiftDefensiveRetainRAII thisRetainHelper{this };
1362
1363
while (true ) {
1363
1364
auto newState = oldState;
1364
1365
@@ -1388,15 +1389,29 @@ void DefaultActorImpl::enqueue(Job *job, JobPriority priority) {
1388
1389
if (needsScheduling || needsStealer)
1389
1390
taskExecutor = TaskExecutorRef::fromTaskExecutorPreference (job);
1390
1391
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
+
1391
1402
// This needs to be a store release so that we also publish the contents of
1392
1403
// the new Job we are adding to the atomic job queue. Pairs with consume
1393
1404
// in drainOne.
1394
1405
if (_status ().compare_exchange_weak (oldState, newState,
1395
1406
/* success */ std::memory_order_release,
1396
1407
/* failure */ std::memory_order_relaxed)) {
1397
1408
// 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.
1399
1412
1413
+ // NOTE: only the pointer value of `this` is used here, so this one
1414
+ // doesn't need a retain.
1400
1415
traceActorStateTransition (this , oldState, newState, distributedActorIsRemote);
1401
1416
1402
1417
if (!oldState.isScheduled () && newState.isScheduled ()) {
@@ -1407,6 +1422,9 @@ void DefaultActorImpl::enqueue(Job *job, JobPriority priority) {
1407
1422
1408
1423
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
1409
1424
if (oldState.getMaxPriority () != newState.getMaxPriority ()) {
1425
+ // We still need `this`, assert that we did a defensive retain.
1426
+ assert (thisRetainHelper.isRetained ());
1427
+
1410
1428
if (newState.isRunning ()) {
1411
1429
// Actor is running on a thread, escalate the thread running it
1412
1430
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) {
1416
1434
} else {
1417
1435
// We are scheduling a stealer for an actor due to priority override.
1418
1436
// 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 ();
1420
1440
SWIFT_TASK_DEBUG_LOG (
1421
1441
" [Override] Scheduling a stealer for actor %p at %#x priority" ,
1422
1442
this , newState.getMaxPriority ());
1423
- swift_retain (this );
1424
1443
1425
1444
scheduleActorProcessJob (newState.getMaxPriority (), taskExecutor);
1426
1445
}
0 commit comments