Skip to content

Commit db4aa81

Browse files
authored
Merge pull request #77079 from mikeash/actor-enqueue-retain-6.0
[6.0][Concurrency] Retain the actor around the CAS in enqueue() when necessary.
2 parents cd8c46f + 8d24648 commit db4aa81

File tree

2 files changed

+73
-3
lines changed

2 files changed

+73
-3
lines changed

include/swift/Runtime/HeapObject.h

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,57 @@ class SwiftRAII {
386386
HeapObject *operator *() const { return object; }
387387
};
388388

389+
/// RAII object that wraps a Swift object and optionally performs a single
390+
/// retain on that object. Multiple requests to retain the object only perform a
391+
/// single retain, and if that retain has been done then it's automatically
392+
/// released when leaving the scope. This helps implement a defensive retain
393+
/// pattern where you may need to retain an object in some circumstances. This
394+
/// helper makes it easy to retain the object only once even when loops are
395+
/// involved, and do a release to balance the retain on all paths out of the
396+
/// scope.
397+
class SwiftDefensiveRetainRAII {
398+
HeapObject *object;
399+
bool didRetain;
400+
401+
public:
402+
// Noncopyable.
403+
SwiftDefensiveRetainRAII(const SwiftDefensiveRetainRAII &) = delete;
404+
SwiftDefensiveRetainRAII &operator=(const SwiftDefensiveRetainRAII &) = delete;
405+
406+
/// Create a new helper with the given object. The object is not retained
407+
/// initially.
408+
SwiftDefensiveRetainRAII(HeapObject *object)
409+
: object(object), didRetain(false) {}
410+
411+
~SwiftDefensiveRetainRAII() {
412+
if (didRetain)
413+
swift_release(object);
414+
}
415+
416+
/// Perform a defensive retain of the object. If a defensive retain has
417+
/// already been performed, this is a no-op.
418+
void defensiveRetain() {
419+
if (!didRetain) {
420+
swift_retain(object);
421+
didRetain = true;
422+
}
423+
}
424+
425+
/// Take the retain from the helper. This is an optimization for code paths
426+
/// that want to retain the object long-term, and avoids doing a redundant
427+
/// retain/release pair. If a defensive retain has not been done, then this
428+
/// will retain the object, so the caller always gets a +1 on the object.
429+
void takeRetain() {
430+
if (!didRetain)
431+
swift_retain(object);
432+
didRetain = false;
433+
}
434+
435+
/// Returns true if the object was defensively retained (and takeRetain not
436+
/// called). Intended for use in asserts.
437+
bool isRetained() { return didRetain; }
438+
};
439+
389440
/*****************************************************************************/
390441
/**************************** UNOWNED REFERENCES *****************************/
391442
/*****************************************************************************/

stdlib/public/Concurrency/Actor.cpp

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)