Skip to content

Commit a7ac32f

Browse files
Disabled isolated deinit for SWIFT_CONCURRENCY_ACTORS_AS_LOCKS in the runtime
1 parent 6fa62f7 commit a7ac32f

File tree

3 files changed

+112
-14
lines changed

3 files changed

+112
-14
lines changed

stdlib/public/Concurrency/Actor.cpp

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2257,6 +2257,24 @@ static void swift_task_deinitOnExecutorImpl(void *object,
22572257
DeinitWorkFunction *work,
22582258
SerialExecutorRef newExecutor,
22592259
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
22602278
// If the current executor is compatible with running the new executor,
22612279
// we can just immediately continue running with the resume function
22622280
// we were passed in.
@@ -2271,12 +2289,7 @@ static void swift_task_deinitOnExecutorImpl(void *object,
22712289
return work(object); // 'return' forces tail call
22722290
}
22732291

2274-
#if SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
2275-
// In this mode taking actor lock is the only possible implementation
2276-
#else
2277-
// Otherwise, it is an optimisation applied when deinitializing default actors
22782292
if (newExecutor.isDefaultActor() && object == newExecutor.getIdentity()) {
2279-
#endif
22802293
// Try to take the lock. This should always succeed, unless someone is
22812294
// running the actor using unsafe unowned reference.
22822295
if (asImpl(newExecutor.getDefaultActor())->tryLock(false)) {
@@ -2316,12 +2329,7 @@ static void swift_task_deinitOnExecutorImpl(void *object,
23162329
// Give up the current actor.
23172330
asImpl(newExecutor.getDefaultActor())->unlock(true);
23182331
return;
2319-
} else {
2320-
#if SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
2321-
assert(false && "Should not enqueue onto default actor in actor as locks model");
2322-
#endif
23232332
}
2324-
#if !SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
23252333
}
23262334

23272335
auto currentTask = swift_task_getCurrent();

test/Concurrency/Runtime/actor_recursive_deinit.swift

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

66
// REQUIRES: concurrency_runtime
77
// UNSUPPORTED: back_deployment_runtime
8+
// UNSUPPORTED: freestanding
89

910
import Swift
1011
import _Concurrency
@@ -34,12 +35,12 @@ func isMainThread() -> Bool {
3435
@_silgen_name("swift_task_isCurrentExecutor")
3536
private func isCurrentExecutor(_ executor: Builtin.Executor) -> Bool
3637

37-
func getExecutor(_ a: AnyActor) -> Builtin.Executor {
38-
let pack = (a, UnsafeRawPointer?.none)
38+
func getExecutor(_ a: any Actor) -> Builtin.Executor {
39+
let pack: (AnyObject, UnsafeRawPointer?) = (a, UnsafeRawPointer?.none)
3940
return unsafeBitCast(pack, to: Builtin.Executor.self)
4041
}
4142

42-
func isCurrent(_ a: AnyActor) -> Bool {
43+
func isCurrent(_ a: any Actor) -> Bool {
4344
return isCurrentExecutor(getExecutor(a))
4445
}
4546

@@ -53,7 +54,7 @@ actor Foo {
5354
}
5455

5556
isolated deinit {
56-
print("DEINIT: \(name) isolated:\(isCurrent(self)) mainThread:\(isMainThread())")
57+
print("DEINIT: \(name) isolated:\(isCurrent(self)) mainThread:\(isMainThread())")
5758
}
5859
}
5960

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift -plugin-path %swift-plugin-dir -Xfrontend -disable-availability-checking -parse-stdlib %s -Xfrontend -concurrency-model=task-to-thread -g -o %t/a.out
3+
// RUN: %target-codesign %t/a.out
4+
// RUN: %env-SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=swift6 %target-run %t/a.out
5+
6+
// REQUIRES: freestanding
7+
// REQUIRES: executable_test
8+
// REQUIRES: concurrency
9+
// REQUIRES: concurrency_runtime
10+
11+
// XFAIL: freestanding
12+
13+
@_spi(_TaskToThreadModel) import _Concurrency
14+
import StdlibUnittest
15+
import Darwin
16+
import Swift
17+
18+
@_silgen_name("swift_task_isCurrentExecutor")
19+
private func isCurrentExecutor(_ executor: Builtin.Executor) -> Bool
20+
21+
private func isCurrentExecutor(_ executor: UnownedSerialExecutor) -> Bool {
22+
isCurrentExecutor(unsafeBitCast(executor, to: Builtin.Executor.self))
23+
}
24+
25+
@available(SwiftStdlib 5.1, *)
26+
struct TL {
27+
@TaskLocal
28+
static var number: Int = 0
29+
}
30+
31+
func checkTaskLocalStack() {
32+
TL.$number.withValue(-999) {
33+
expectEqual(-999, TL.number)
34+
}
35+
}
36+
37+
actor ActorNoOp {
38+
let expectedNumber: Int
39+
let probe: Probe
40+
41+
init(expectedNumber: Int) {
42+
self.expectedNumber = expectedNumber
43+
self.probe = Probe(expectedNumber: expectedNumber)
44+
self.probe.probeExpectedExecutor = self.unownedExecutor
45+
}
46+
47+
isolated deinit {
48+
expectTrue(isCurrentExecutor(self.unownedExecutor))
49+
expectEqual(expectedNumber, TL.number)
50+
checkTaskLocalStack()
51+
}
52+
}
53+
54+
class Probe {
55+
var probeExpectedExecutor: UnownedSerialExecutor!
56+
let probeExpectedNumber: Int
57+
58+
init(expectedNumber: Int) {
59+
self.probeExpectedExecutor = nil
60+
self.probeExpectedNumber = expectedNumber
61+
}
62+
63+
deinit {
64+
expectTrue(isCurrentExecutor(probeExpectedExecutor))
65+
expectEqual(probeExpectedNumber, TL.number)
66+
checkTaskLocalStack()
67+
}
68+
}
69+
70+
let tests = TestSuite("Isolated Deinit")
71+
72+
if #available(SwiftStdlib 5.1, *) {
73+
tests.test("actor sync not isolated") {
74+
Task.runInline {
75+
TL.$number.withValue(99) {
76+
// Freestanding runtime always executes deinit inline
77+
_ = ActorNoOp(expectedNumber: 99)
78+
}
79+
}
80+
}
81+
82+
tests.test("no TLs") {
83+
Task.runInline {
84+
_ = ActorNoOp(expectedNumber: 0)
85+
}
86+
}
87+
}
88+
89+
runAllTests()

0 commit comments

Comments
 (0)