1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
|
// RUN: %empty-directory(%t)
// RUN: %target-build-swift -enable-experimental-feature IsolatedDeinit -plugin-path %swift-plugin-dir -target %target-future-triple -parse-stdlib %import-libdispatch %s -o %t/a.out
// RUN: %target-codesign %t/a.out
// RUN: %env-SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=swift6 %target-run %t/a.out
// REQUIRES: libdispatch
// REQUIRES: executable_test
// REQUIRES: concurrency
// REQUIRES: concurrency_runtime
// UNSUPPORTED: back_deployment_runtime
// REQUIRES: swift_feature_IsolatedDeinit
import Swift
import _Concurrency
import Dispatch
import StdlibUnittest
@_silgen_name("swift_task_isCurrentExecutor")
private func isCurrentExecutor(_ executor: Builtin.Executor) -> Bool
private func isCurrentExecutor(_ executor: UnownedSerialExecutor) -> Bool {
isCurrentExecutor(unsafeBitCast(executor, to: Builtin.Executor.self))
}
extension DispatchGroup {
func enter(_ count: Int) {
for _ in 0..<count {
self.enter()
}
}
}
@available(SwiftStdlib 5.1, *)
struct TL {
@TaskLocal
static var number: Int = 0
}
func checkTaskLocalStack() {
TL.$number.withValue(-999) {
expectEqual(-999, TL.number)
}
}
actor ActorNoOp {
let expectedNumber: Int
let group: DispatchGroup
let probe: Probe
init(expectedNumber: Int, group: DispatchGroup) {
self.expectedNumber = expectedNumber
self.group = group
self.probe = Probe(expectedNumber: expectedNumber, group: group)
self.probe.probeExpectedExecutor = self.unownedExecutor
}
isolated deinit {
expectTrue(isCurrentExecutor(self.unownedExecutor))
expectEqual(expectedNumber, TL.number)
checkTaskLocalStack()
group.leave()
}
}
actor ProxyActor: Actor {
private let impl: ActorNoOp
init(expectedNumber: Int, group: DispatchGroup) {
self.impl = ActorNoOp(expectedNumber: expectedNumber, group: group)
}
isolated deinit {}
nonisolated var unownedExecutor: UnownedSerialExecutor {
return impl.unownedExecutor
}
}
@globalActor actor AnotherActor: GlobalActor {
static let shared = AnotherActor()
func performTesting(_ work: @Sendable () -> Void) {
work()
}
}
class Probe {
var probeExpectedExecutor: UnownedSerialExecutor
let probeExpectedNumber: Int
let probeGroup: DispatchGroup
init(expectedNumber: Int, group: DispatchGroup) {
self.probeExpectedExecutor = AnotherActor.shared.unownedExecutor
self.probeExpectedNumber = expectedNumber
self.probeGroup = group
group.enter()
}
deinit {
expectTrue(isCurrentExecutor(probeExpectedExecutor))
expectEqual(probeExpectedNumber, TL.number)
checkTaskLocalStack()
probeGroup.leave()
}
}
class ClassNoOp: Probe {
let expectedNumber: Int
let group: DispatchGroup
let probe: Probe
override init(expectedNumber: Int, group: DispatchGroup) {
self.expectedNumber = expectedNumber
self.group = group
self.probe = Probe(expectedNumber: expectedNumber, group: group)
super.init(expectedNumber: expectedNumber, group: group)
}
@AnotherActor
deinit {
expectTrue(isCurrentExecutor(AnotherActor.shared.unownedExecutor))
expectEqual(expectedNumber, TL.number)
checkTaskLocalStack()
group.leave()
}
}
let tests = TestSuite("Isolated Deinit")
if #available(SwiftStdlib 5.1, *) {
tests.test("class sync fast path") {
let group = DispatchGroup()
group.enter(1)
Task {
// FIXME: isolated deinit should be clearing task locals
await TL.$number.withValue(42) {
await AnotherActor.shared.performTesting {
_ = ClassNoOp(expectedNumber: 0, group: group)
}
}
}
group.wait()
}
tests.test("class sync slow path") {
let group = DispatchGroup()
group.enter(1)
Task {
TL.$number.withValue(99) {
_ = ClassNoOp(expectedNumber: 0, group: group)
}
}
group.wait()
}
tests.test("actor sync fast path") {
let group = DispatchGroup()
group.enter(1)
Task {
// FIXME: isolated deinit should be clearing task locals
TL.$number.withValue(99) {
// Despite last release happening not on the actor itself,
// this is still a fast path due to optimisation for deallocating actors.
_ = ActorNoOp(expectedNumber: 0, group: group)
}
}
group.wait()
}
tests.test("actor sync slow path") {
let group = DispatchGroup()
group.enter(1)
Task {
TL.$number.withValue(99) {
// Using ProxyActor breaks optimization
_ = ProxyActor(expectedNumber: 0, group: group)
}
}
group.wait()
}
tests.test("no TLs") {
let group = DispatchGroup()
group.enter(2)
Task {
_ = ActorNoOp(expectedNumber: 0, group: group)
_ = ClassNoOp(expectedNumber: 0, group: group)
}
group.wait()
}
}
runAllTests()
|