Skip to content

Commit 2354ce3

Browse files
Added more tests for isolated actor deinit:
* Recursive deallocation with multiple locks held * (UB) Escaping self triggers assertion in stdlib
1 parent 2ad8df8 commit 2354ce3

File tree

2 files changed

+152
-0
lines changed

2 files changed

+152
-0
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking -parse-as-library)
2+
3+
// REQUIRES: executable_test
4+
// REQUIRES: concurrency
5+
// REQUIRES: concurrency_runtime
6+
// UNSUPPORTED: back_deployment_runtime
7+
8+
import _Concurrency
9+
@preconcurrency import Dispatch
10+
import StdlibUnittest
11+
12+
actor EscapeLocked {
13+
var k: Int = 1
14+
15+
func increment() {
16+
k += 1
17+
}
18+
19+
deinit {
20+
let g = DispatchGroup()
21+
g.enter()
22+
Task.detached {
23+
await self.increment()
24+
g.leave()
25+
}
26+
let r = g.wait(timeout: .now() + .milliseconds(500))
27+
expectEqual(r, .timedOut)
28+
expectCrashLater(withMessage: "Assertion failed: (!oldState.getFirstJob() && \"actor has queued jobs at destruction\"), function destroy")
29+
}
30+
}
31+
32+
actor EscapeUnlocked {
33+
let cont: UnsafeContinuation<Void, Never>
34+
var k: Int = 1
35+
36+
init(_ cont: UnsafeContinuation<Void, Never>) {
37+
self.cont = cont
38+
}
39+
40+
func increment() {
41+
k += 1
42+
}
43+
44+
deinit {
45+
DispatchQueue.main.async {
46+
Task.detached {
47+
expectCrashLater(withMessage: "Assertion failed: (oldState.getMaxPriority() == JobPriority::Unspecified), function tryLock")
48+
await self.increment()
49+
self.cont.resume()
50+
}
51+
}
52+
}
53+
}
54+
55+
@main struct Main {
56+
static func main() async {
57+
let tests = TestSuite("EscapingSelf")
58+
tests.test("escape while locked") {
59+
_ = EscapeLocked()
60+
}
61+
62+
tests.test("escape while unlocked") {
63+
await withUnsafeContinuation { cont in
64+
_ = EscapeUnlocked(cont)
65+
}
66+
}
67+
await runAllTestsAsync()
68+
}
69+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// RUN: %target-run-simple-swift(-Xfrontend -disable-availability-checking -parse-as-library) | %FileCheck %s
2+
3+
// REQUIRES: executable_test
4+
// REQUIRES: concurrency
5+
6+
// REQUIRES: concurrency_runtime
7+
// UNSUPPORTED: back_deployment_runtime
8+
9+
#if canImport(Darwin)
10+
import Darwin
11+
typealias ThreadID = pthread_t
12+
func getCurrentThreadID() -> ThreadID { pthread_self() }
13+
func equalThreadIDs(_ a: ThreadID, _ b: ThreadID) -> Bool { pthread_equal(a, b) != 0 }
14+
#elseif canImport(Glibc)
15+
import Glibc
16+
typealias ThreadID = pthread_t
17+
func getCurrentThreadID() -> ThreadID { pthread_self() }
18+
func equalThreadIDs(_ a: ThreadID, _ b: ThreadID) -> Bool { pthread_equal(a, b) != 0 }
19+
#elseif os(Windows)
20+
import WinSDK
21+
typealias ThreadID = UInt32
22+
func getCurrentThreadID() -> ThreadID { GetCurrentThreadId() }
23+
func equalThreadIDs(_ a: ThreadID, _ b: ThreadID) -> Bool { a == b }
24+
#endif
25+
26+
var mainThread: ThreadID?
27+
func isMainThread() -> Bool {
28+
return equalThreadIDs(getCurrentThreadID(), mainThread!)
29+
}
30+
31+
// Look up a symbol using dlsym and cast the result to an arbitrary type.
32+
func lookup<T>(_ name: String) -> T {
33+
let RTLD_DEFAULT = UnsafeMutableRawPointer(bitPattern: -2)
34+
let result = dlsym(RTLD_DEFAULT, name)
35+
return unsafeBitCast(result, to: T.self)
36+
}
37+
38+
let getMainExecutor = lookup("swift_task_getMainExecutor") as @convention(thin) () -> UnownedSerialExecutor
39+
let isCurrentExecutor = lookup("swift_task_isCurrentExecutor") as @convention(thin) (UnownedSerialExecutor) -> Bool
40+
41+
func getExecutor(_ a: AnyActor) -> UnownedSerialExecutor {
42+
let pack = (a, UnsafeRawPointer?.none)
43+
return unsafeBitCast(pack, to: UnownedSerialExecutor.self)
44+
}
45+
46+
func isCurrent(_ a: AnyActor) -> Bool {
47+
return isCurrentExecutor(getExecutor(a))
48+
}
49+
50+
func isMainExecutor() -> Bool {
51+
isCurrentExecutor(getMainExecutor())
52+
}
53+
54+
actor Foo {
55+
let name: String
56+
let child: Foo?
57+
58+
init(_ name: String, _ child: Foo?) {
59+
self.name = name
60+
self.child = child
61+
}
62+
63+
deinit {
64+
print("DEINIT: \(name) isolated:\(isCurrent(self)) mainThread:\(isMainThread())")
65+
}
66+
}
67+
68+
// CHECK: DEINIT: a isolated:true mainThread:true
69+
// CHECK: DEINIT: b isolated:true mainThread:true
70+
// CHECK: DEINIT: c isolated:true mainThread:true
71+
// CHECK: DEINIT: d isolated:true mainThread:true
72+
// CHECK: DONE
73+
74+
@main
75+
struct Main {
76+
static func main() async {
77+
mainThread = getCurrentThreadID()
78+
do {
79+
_ = Foo("a", Foo("b", Foo("c", Foo("d", nil))))
80+
}
81+
print("DONE")
82+
}
83+
}

0 commit comments

Comments
 (0)