mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
This avoids the potential to race with the triggering coming from task_cancel, because we first set the cancelled flag, and only THEN take the lock and iterate over the inserted records. Because of this we could: T1 flip the cancelled bit; T2 observes that, and triggers "immediately" during installing the handler record. T1 then proceeds to lock records and trigger it again, causing a double trigger of the cancellation handler. resolves https://github.com/swiftlang/swift/issues/80161 resolves rdar://147493150
63 lines
1.6 KiB
Swift
63 lines
1.6 KiB
Swift
// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking -target %target-swift-5.1-abi-triple %import-libdispatch) | %FileCheck %s
|
|
// REQUIRES: concurrency
|
|
// REQUIRES: executable_test
|
|
|
|
// rdar://76038845
|
|
// REQUIRES: concurrency_runtime
|
|
// UNSUPPORTED: back_deployment_runtime
|
|
// UNSUPPORTED: freestanding
|
|
|
|
import Synchronization
|
|
|
|
struct State {
|
|
var cancelled = 0
|
|
var continuation: CheckedContinuation<Void, Never>?
|
|
}
|
|
|
|
func testFunc(_ iteration: Int) async -> Task<Void, Never> {
|
|
let state = Mutex(State())
|
|
|
|
let task = Task {
|
|
await withTaskCancellationHandler {
|
|
await withCheckedContinuation { continuation in
|
|
let cancelled = state.withLock {
|
|
if $0.cancelled > 0 {
|
|
return true
|
|
} else {
|
|
$0.continuation = continuation
|
|
return false
|
|
}
|
|
}
|
|
if cancelled {
|
|
continuation.resume()
|
|
}
|
|
}
|
|
} onCancel: {
|
|
let continuation = state.withLock {
|
|
$0.cancelled += 1
|
|
return $0.continuation.take()
|
|
}
|
|
continuation?.resume()
|
|
}
|
|
}
|
|
|
|
// This task cancel is racing with installing the cancellation handler,
|
|
// and we may either hit the cancellation handler:
|
|
// - after this cancel was issued, and therefore the handler runs immediately
|
|
task.cancel()
|
|
_ = await task.value
|
|
|
|
let cancelled = state.withLock { $0.cancelled }
|
|
precondition(cancelled == 1, "cancelled more than once, iteration: \(iteration)")
|
|
|
|
return task
|
|
}
|
|
|
|
var ts: [Task<Void, Never>] = []
|
|
for iteration in 0..<1_000 {
|
|
let t = await testFunc(iteration)
|
|
ts.append(t)
|
|
}
|
|
|
|
print("done") // CHECK: done
|