Skip to content

Commit bd85d83

Browse files
authored
Merge pull request swiftlang#35392 from jckarter/checked-continuation-trap
Concurrency: Have CheckedContinuation trap on double resume.
2 parents fcbc2b4 + 49d4299 commit bd85d83

File tree

2 files changed

+44
-35
lines changed

2 files changed

+44
-35
lines changed

stdlib/public/Concurrency/CheckedContinuation.swift

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,11 @@ internal final class CheckedContinuationCanary {
6868

6969
internal func takeContinuation<T>() -> UnsafeContinuation<T>? {
7070
return unsafeBitCast(_takeContinuation(),
71-
to: UnsafeContinuation<T>.self)
71+
to: UnsafeContinuation<T>?.self)
7272
}
7373
internal func takeThrowingContinuation<T>() -> UnsafeThrowingContinuation<T>? {
7474
return unsafeBitCast(_takeContinuation(),
75-
to: UnsafeThrowingContinuation<T>.self)
75+
to: UnsafeThrowingContinuation<T>?.self)
7676
}
7777

7878
deinit {
@@ -137,12 +137,16 @@ public struct CheckedContinuation<T> {
137137
///
138138
/// A continuation must be resumed exactly once. If the continuation has
139139
/// already been resumed through this object, then the attempt to resume
140-
/// the continuation again will be logged, but otherwise have no effect.
140+
/// the continuation again will trap.
141+
///
142+
/// After `resume` enqueues the task, control is immediately returned to
143+
/// the caller. The task will continue executing when its executor is
144+
/// able to reschedule it.
141145
public func resume(returning x: __owned T) {
142146
if let c: UnsafeContinuation<T> = canary.takeContinuation() {
143147
c.resume(returning: x)
144148
} else {
145-
logFailedCheck("SWIFT TASK CONTINUATION MISUSE: \(canary.function) tried to resume its continuation more than once, returning \(x)!\n")
149+
fatalError("SWIFT TASK CONTINUATION MISUSE: \(canary.function) tried to resume its continuation more than once, returning \(x)!\n")
146150
}
147151
}
148152
}
@@ -207,29 +211,35 @@ public struct CheckedThrowingContinuation<T> {
207211
/// from its suspension point.
208212
///
209213
/// A continuation must be resumed exactly once. If the continuation has
210-
/// already been resumed through this object, whether by `resume(returning:)`
211-
/// or by `resume(throwing:)`, then the attempt to resume
212-
/// the continuation again will be logged, but otherwise have no effect.
214+
/// already been resumed through this object, then the attempt to resume
215+
/// the continuation again will trap.
216+
///
217+
/// After `resume` enqueues the task, control is immediately returned to
218+
/// the caller. The task will continue executing when its executor is
219+
/// able to reschedule it.
213220
public func resume(returning x: __owned T) {
214221
if let c: UnsafeThrowingContinuation<T> = canary.takeThrowingContinuation() {
215222
c.resume(returning: x)
216223
} else {
217-
logFailedCheck("SWIFT TASK CONTINUATION MISUSE: \(canary.function) tried to resume its continuation more than once, returning \(x)!\n")
224+
fatalError("SWIFT TASK CONTINUATION MISUSE: \(canary.function) tried to resume its continuation more than once, returning \(x)!\n")
218225
}
219226
}
220227

221228
/// Resume the task awaiting the continuation by having it throw an error
222229
/// from its suspension point.
223230
///
224231
/// A continuation must be resumed exactly once. If the continuation has
225-
/// already been resumed through this object, whether by `resume(returning:)`
226-
/// or by `resume(throwing:)`, then the attempt to resume
227-
/// the continuation again will be logged, but otherwise have no effect.
232+
/// already been resumed through this object, then the attempt to resume
233+
/// the continuation again will trap.
234+
///
235+
/// After `resume` enqueues the task, control is immediately returned to
236+
/// the caller. The task will continue executing when its executor is
237+
/// able to reschedule it.
228238
public func resume(throwing x: __owned Error) {
229239
if let c: UnsafeThrowingContinuation<T> = canary.takeThrowingContinuation() {
230240
c.resume(throwing: x)
231241
} else {
232-
logFailedCheck("SWIFT TASK CONTINUATION MISUSE: \(canary.function) tried to resume its continuation more than once, throwing \(x)!\n")
242+
fatalError("SWIFT TASK CONTINUATION MISUSE: \(canary.function) tried to resume its continuation more than once, throwing \(x)!\n")
233243
}
234244
}
235245
}
Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,36 @@
1-
// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-concurrency) 2>&1 | %FileCheck %s
1+
// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-concurrency)
22

33
// REQUIRES: executable_test
44
// REQUIRES: concurrency
55

66
import _Concurrency
7+
import StdlibUnittest
78

89
struct TestError: Error {}
910

10-
runAsyncAndBlock {
11-
let x: Int = await withCheckedContinuation { c in
12-
c.resume(returning: 17)
13-
c.resume(returning: 38)
11+
var tests = TestSuite("CheckedContinuation")
12+
13+
tests.test("trap on double resume non-throwing continuation") {
14+
expectCrashLater()
15+
runAsyncAndBlock {
16+
let _: Int = await withCheckedContinuation { c in
17+
c.resume(returning: 17)
18+
c.resume(returning: 38)
1419
}
15-
// CHECK: main tried to resume its continuation more than once, returning 38!
20+
}
21+
}
1622

23+
tests.test("trap on double resume throwing continuation") {
24+
expectCrashLater()
25+
runAsyncAndBlock {
1726
do {
18-
let x: Int = await try withCheckedThrowingContinuation { c in
19-
c.resume(throwing: TestError())
20-
c.resume(returning: 679)
21-
c.resume(throwing: TestError())
22-
}
27+
let _: Int = try await withCheckedThrowingContinuation { c in
28+
c.resume(returning: 17)
29+
c.resume(throwing: TestError())
30+
}
2331
} catch {
24-
// CHECK-NEXT: main tried to resume its continuation more than once, returning 679!
25-
// CHECK-NEXT: main tried to resume its continuation more than once, throwing TestError()!
26-
}
27-
28-
_ = Task.runDetached {
29-
let _: Void = await withCheckedContinuation { _ in
30-
/*do nothing, leaking the task*/
31-
}
32-
// TODO: Whether the detached task gets a chance to run or not before
33-
// the process exits is currently platform-dependent, and we don't yet
34-
// have the API for yielding to the task runtime.
35-
// C/HECK-NEXT: main leaked its continuation!
3632
}
33+
}
3734
}
35+
36+
runAllTests()

0 commit comments

Comments
 (0)