Skip to content

Commit d6673c7

Browse files
Implemented zombie actor state in SWIFT_CONCURRENCY_ACTORS_AS_LOCKS mode using additional reference counter
1 parent a7ac32f commit d6673c7

File tree

3 files changed

+44
-25
lines changed

3 files changed

+44
-25
lines changed

stdlib/public/Concurrency/Actor.cpp

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,11 @@ class DefaultActorImplHeader : public HeapObject {
10341034
// synchronously, no job queue is needed and the lock will handle all priority
10351035
// escalation logic
10361036
Mutex drainLock;
1037+
// Actor can be deinitialized while lock is still being held.
1038+
// We maintain separate reference counter for the lock to make sure that
1039+
// lock is destroyed and memory is freed when both actor is deinitialized
1040+
// and lock is unlocked.
1041+
std::atomic<int> lockReferenceCount;
10371042
#else
10381043
// Note: There is some padding that is added here by the compiler in order to
10391044
// enforce alignment. This is space that is available for us to use in
@@ -1130,6 +1135,7 @@ class DefaultActorImpl
11301135
this->isDistributedRemoteActor = isDistributedRemote;
11311136
#if SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
11321137
new (&this->drainLock) Mutex();
1138+
lockReferenceCount = 1;
11331139
#else
11341140
_status().store(ActiveActorStatus(), std::memory_order_relaxed);
11351141
new (&this->prioritizedJobs) PriorityQueue();
@@ -1152,6 +1158,11 @@ class DefaultActorImpl
11521158
/// Unlock an actor
11531159
bool unlock(bool forceUnlock);
11541160

1161+
#if SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
1162+
void retainLock();
1163+
void releaseLock();
1164+
#endif
1165+
11551166
#if !SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
11561167
/// Enqueue a job onto the actor.
11571168
void enqueue(Job *job, JobPriority priority);
@@ -1688,7 +1699,9 @@ void DefaultActorImpl::destroy() {
16881699
}
16891700

16901701
void DefaultActorImpl::deallocate() {
1691-
#if !SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
1702+
#if SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
1703+
releaseLock();
1704+
#else
16921705
// If we're running, mark ourselves as ready for deallocation but don't
16931706
// deallocate yet. When we stop running the actor - at unlock() time - we'll
16941707
// do the actual deallocation.
@@ -1704,8 +1717,8 @@ void DefaultActorImpl::deallocate() {
17041717
}
17051718

17061719
assert(oldState.isIdle());
1707-
#endif
17081720
deallocateUnconditional();
1721+
#endif
17091722
}
17101723

17111724
void DefaultActorImpl::deallocateUnconditional() {
@@ -1718,6 +1731,7 @@ void DefaultActorImpl::deallocateUnconditional() {
17181731

17191732
bool DefaultActorImpl::tryLock(bool asDrainer) {
17201733
#if SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
1734+
retainLock();
17211735
this->drainLock.lock();
17221736
return true;
17231737
#else /* SWIFT_CONCURRENCY_ACTORS_AS_LOCKS */
@@ -1830,6 +1844,7 @@ bool DefaultActorImpl::unlock(bool forceUnlock)
18301844
{
18311845
#if SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
18321846
this->drainLock.unlock();
1847+
releaseLock();
18331848
return true;
18341849
#else
18351850
bool distributedActorIsRemote = swift_distributed_actor_is_remote(this);
@@ -1919,6 +1934,18 @@ bool DefaultActorImpl::unlock(bool forceUnlock)
19191934
#endif
19201935
}
19211936

1937+
#if SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
1938+
void DefaultActorImpl::retainLock() {
1939+
lockReferenceCount.fetch_add(1, std::memory_order_acquire);
1940+
}
1941+
void DefaultActorImpl::releaseLock() {
1942+
if (1 == lockReferenceCount.fetch_add(-1, std::memory_order_release)) {
1943+
drainLock.~Mutex();
1944+
deallocateUnconditional();
1945+
}
1946+
}
1947+
#endif
1948+
19221949
SWIFT_CC(swift)
19231950
static void swift_job_runImpl(Job *job, SerialExecutorRef executor) {
19241951
ExecutorTrackingInfo trackingInfo;
@@ -2257,24 +2284,6 @@ static void swift_task_deinitOnExecutorImpl(void *object,
22572284
DeinitWorkFunction *work,
22582285
SerialExecutorRef newExecutor,
22592286
size_t rawFlags) {
2260-
#if SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
2261-
// To properly support isolated deinit in this mode, we
2262-
// need to be able to postpone deallocation of the lock
2263-
// until actor is unlocked.
2264-
//
2265-
// Note that such zombie state probably should be supported regardless of the isolated deinit.
2266-
// Theoretically it is possible for last release to happen in the middle of a job isolated to the actor.
2267-
// But until isolated consuming parameters are fixed, this seems to be impossible to reproduce.
2268-
// See also https://github.com/swiftlang/swift/issues/76083
2269-
//
2270-
// Alternatively we could lock and unlock before executing deinit body.
2271-
// This would be sufficient to wait until all jobs isolated to the actor have finished.
2272-
// Any code attempting to take actor's lock while deinit is running is incorrect anyway.
2273-
// So it does not matter much if deinit body is executed with lock held or not.
2274-
//
2275-
// But this workaround applies only to the isolated deinit and does not solve the generic case.
2276-
swift_Concurrency_fatalError(0, "Isolated deinit is not yet supported in actor as locks model");
2277-
#else
22782287
// If the current executor is compatible with running the new executor,
22792288
// we can just immediately continue running with the resume function
22802289
// we were passed in.
@@ -2289,7 +2298,12 @@ static void swift_task_deinitOnExecutorImpl(void *object,
22892298
return work(object); // 'return' forces tail call
22902299
}
22912300

2301+
#if SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
2302+
// In this mode taking actor lock is the only possible implementation
2303+
#else
2304+
// Otherwise, it is an optimisation applied when deinitializing default actors
22922305
if (newExecutor.isDefaultActor() && object == newExecutor.getIdentity()) {
2306+
#endif
22932307
// Try to take the lock. This should always succeed, unless someone is
22942308
// running the actor using unsafe unowned reference.
22952309
if (asImpl(newExecutor.getDefaultActor())->tryLock(false)) {
@@ -2329,9 +2343,13 @@ static void swift_task_deinitOnExecutorImpl(void *object,
23292343
// Give up the current actor.
23302344
asImpl(newExecutor.getDefaultActor())->unlock(true);
23312345
return;
2346+
} else {
2347+
#if SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
2348+
assert(false && "Should not enqueue onto default actor in actor as locks model");
2349+
#endif
23322350
}
2351+
#if !SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
23332352
}
2334-
23352353
auto currentTask = swift_task_getCurrent();
23362354
auto priority = currentTask ? swift_task_currentPriority(currentTask)
23372355
: swift_task_getCurrentThreadPriority();

test/Concurrency/Runtime/actor_recursive_deinit.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55

66
// REQUIRES: concurrency_runtime
77
// UNSUPPORTED: back_deployment_runtime
8-
// UNSUPPORTED: freestanding
8+
9+
// Compiler crashes because builtin "ifdef_SWIFT_STDLIB_PRINT_DISABLED"() gets lowered as "i32 0",
10+
// which triggers assertion in LLVM, which expects it to be i1
11+
// XFAIL: freestanding
912

1013
import Swift
1114
import _Concurrency
@@ -54,7 +57,7 @@ actor Foo {
5457
}
5558

5659
isolated deinit {
57-
print("DEINIT: \(name) isolated:\(isCurrent(self)) mainThread:\(isMainThread())")
60+
print("DEINIT: \(self.name) isolated:\(isCurrent(self)) mainThread:\(isMainThread())")
5861
}
5962
}
6063

test/Concurrency/Runtime/async_task_locals_isolated_deinit_freestanding.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
// REQUIRES: concurrency
99
// REQUIRES: concurrency_runtime
1010

11-
// XFAIL: freestanding
12-
1311
@_spi(_TaskToThreadModel) import _Concurrency
1412
import StdlibUnittest
1513
import Darwin

0 commit comments

Comments
 (0)