@@ -1034,6 +1034,11 @@ class DefaultActorImplHeader : public HeapObject {
1034
1034
// synchronously, no job queue is needed and the lock will handle all priority
1035
1035
// escalation logic
1036
1036
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;
1037
1042
#else
1038
1043
// Note: There is some padding that is added here by the compiler in order to
1039
1044
// enforce alignment. This is space that is available for us to use in
@@ -1130,6 +1135,7 @@ class DefaultActorImpl
1130
1135
this ->isDistributedRemoteActor = isDistributedRemote;
1131
1136
#if SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
1132
1137
new (&this ->drainLock ) Mutex ();
1138
+ lockReferenceCount = 1 ;
1133
1139
#else
1134
1140
_status ().store (ActiveActorStatus (), std::memory_order_relaxed);
1135
1141
new (&this ->prioritizedJobs ) PriorityQueue ();
@@ -1152,6 +1158,11 @@ class DefaultActorImpl
1152
1158
// / Unlock an actor
1153
1159
bool unlock (bool forceUnlock);
1154
1160
1161
+ #if SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
1162
+ void retainLock ();
1163
+ void releaseLock ();
1164
+ #endif
1165
+
1155
1166
#if !SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
1156
1167
// / Enqueue a job onto the actor.
1157
1168
void enqueue (Job *job, JobPriority priority);
@@ -1688,7 +1699,9 @@ void DefaultActorImpl::destroy() {
1688
1699
}
1689
1700
1690
1701
void DefaultActorImpl::deallocate () {
1691
- #if !SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
1702
+ #if SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
1703
+ releaseLock ();
1704
+ #else
1692
1705
// If we're running, mark ourselves as ready for deallocation but don't
1693
1706
// deallocate yet. When we stop running the actor - at unlock() time - we'll
1694
1707
// do the actual deallocation.
@@ -1704,8 +1717,8 @@ void DefaultActorImpl::deallocate() {
1704
1717
}
1705
1718
1706
1719
assert (oldState.isIdle ());
1707
- #endif
1708
1720
deallocateUnconditional ();
1721
+ #endif
1709
1722
}
1710
1723
1711
1724
void DefaultActorImpl::deallocateUnconditional () {
@@ -1718,6 +1731,7 @@ void DefaultActorImpl::deallocateUnconditional() {
1718
1731
1719
1732
bool DefaultActorImpl::tryLock (bool asDrainer) {
1720
1733
#if SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
1734
+ retainLock ();
1721
1735
this ->drainLock .lock ();
1722
1736
return true ;
1723
1737
#else /* SWIFT_CONCURRENCY_ACTORS_AS_LOCKS */
@@ -1830,6 +1844,7 @@ bool DefaultActorImpl::unlock(bool forceUnlock)
1830
1844
{
1831
1845
#if SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
1832
1846
this ->drainLock .unlock ();
1847
+ releaseLock ();
1833
1848
return true ;
1834
1849
#else
1835
1850
bool distributedActorIsRemote = swift_distributed_actor_is_remote (this );
@@ -1919,6 +1934,18 @@ bool DefaultActorImpl::unlock(bool forceUnlock)
1919
1934
#endif
1920
1935
}
1921
1936
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
+
1922
1949
SWIFT_CC (swift)
1923
1950
static void swift_job_runImpl(Job *job, SerialExecutorRef executor) {
1924
1951
ExecutorTrackingInfo trackingInfo;
@@ -2257,24 +2284,6 @@ static void swift_task_deinitOnExecutorImpl(void *object,
2257
2284
DeinitWorkFunction *work,
2258
2285
SerialExecutorRef newExecutor,
2259
2286
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
2278
2287
// If the current executor is compatible with running the new executor,
2279
2288
// we can just immediately continue running with the resume function
2280
2289
// we were passed in.
@@ -2289,7 +2298,12 @@ static void swift_task_deinitOnExecutorImpl(void *object,
2289
2298
return work (object); // 'return' forces tail call
2290
2299
}
2291
2300
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
2292
2305
if (newExecutor.isDefaultActor () && object == newExecutor.getIdentity ()) {
2306
+ #endif
2293
2307
// Try to take the lock. This should always succeed, unless someone is
2294
2308
// running the actor using unsafe unowned reference.
2295
2309
if (asImpl (newExecutor.getDefaultActor ())->tryLock (false )) {
@@ -2329,9 +2343,13 @@ static void swift_task_deinitOnExecutorImpl(void *object,
2329
2343
// Give up the current actor.
2330
2344
asImpl (newExecutor.getDefaultActor ())->unlock (true );
2331
2345
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
2332
2350
}
2351
+ #if !SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
2333
2352
}
2334
-
2335
2353
auto currentTask = swift_task_getCurrent ();
2336
2354
auto priority = currentTask ? swift_task_currentPriority (currentTask)
2337
2355
: swift_task_getCurrentThreadPriority ();
0 commit comments