Skip to content

Commit 7c63adf

Browse files
committed
[Concurrency] Correct withContinuation APIs isolation handling
rdar://125307764
1 parent 7f74e7a commit 7c63adf

File tree

5 files changed

+121
-11
lines changed

5 files changed

+121
-11
lines changed

stdlib/public/Concurrency/CheckedContinuation.swift

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -282,18 +282,32 @@ extension CheckedContinuation {
282282
/// - SeeAlso: `withUnsafeContinuation(function:_:)`
283283
/// - SeeAlso: `withUnsafeThrowingContinuation(function:_:)`
284284
@available(SwiftStdlib 5.1, *)
285-
@_unsafeInheritExecutor // ABI compatibility with Swift 5.1
286285
@inlinable
287286
@_unavailableInEmbedded
288287
public func withCheckedContinuation<T>(
289-
function: String = #function,
290-
_ body: (CheckedContinuation<T, Never>) -> Void
288+
isolation: isolated (any Actor)? = #isolation,
289+
function: String = #function,
290+
_ body: (CheckedContinuation<T, Never>) -> Void
291291
) async -> T {
292292
return await withUnsafeContinuation {
293293
body(CheckedContinuation(continuation: $0, function: function))
294294
}
295295
}
296296

297+
@available(SwiftStdlib 5.1, *)
298+
@_unsafeInheritExecutor // ABI compatibility with Swift 5.1
299+
@inlinable
300+
@_unavailableInEmbedded
301+
public func __abi_withCheckedContinuation<T>(
302+
function: String = #function,
303+
_ body: (CheckedContinuation<T, Never>) -> Void
304+
) async -> T {
305+
return await __abi_withUnsafeContinuation {
306+
body(CheckedContinuation(continuation: $0, function: function))
307+
}
308+
}
309+
310+
297311
/// Invokes the passed in closure with a checked continuation for the current task.
298312
///
299313
/// The body of the closure executes synchronously on the calling task, and once it returns
@@ -323,18 +337,31 @@ public func withCheckedContinuation<T>(
323337
/// - SeeAlso: `withUnsafeContinuation(function:_:)`
324338
/// - SeeAlso: `withUnsafeThrowingContinuation(function:_:)`
325339
@available(SwiftStdlib 5.1, *)
326-
@_unsafeInheritExecutor // ABI compatibility with Swift 5.1
327340
@inlinable
328341
@_unavailableInEmbedded
329342
public func withCheckedThrowingContinuation<T>(
330-
function: String = #function,
331-
_ body: (CheckedContinuation<T, Error>) -> Void
343+
isolation: isolated (any Actor)? = #isolation,
344+
function: String = #function,
345+
_ body: (CheckedContinuation<T, Error>) -> Void
332346
) async throws -> T {
333347
return try await withUnsafeThrowingContinuation {
334348
body(CheckedContinuation(continuation: $0, function: function))
335349
}
336350
}
337351

352+
@available(SwiftStdlib 5.1, *)
353+
@_unsafeInheritExecutor // ABI compatibility with Swift 5.1
354+
@inlinable
355+
@_unavailableInEmbedded
356+
public func __abi_withCheckedThrowingContinuation<T>(
357+
function: String = #function,
358+
_ body: (CheckedContinuation<T, Error>) -> Void
359+
) async throws -> T {
360+
return try await __abi_withUnsafeThrowingContinuation {
361+
body(CheckedContinuation(continuation: $0, function: function))
362+
}
363+
}
364+
338365
#if _runtime(_ObjC)
339366

340367
// Intrinsics used by SILGen to create, resume, or fail checked continuations.

stdlib/public/Concurrency/PartialAsyncTask.swift

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -598,9 +598,20 @@ internal func _resumeUnsafeThrowingContinuationWithError<T>(
598598
/// - SeeAlso: `withCheckedContinuation(function:_:)`
599599
/// - SeeAlso: `withCheckedThrowingContinuation(function:_:)`
600600
@available(SwiftStdlib 5.1, *)
601-
@_unsafeInheritExecutor
602601
@_alwaysEmitIntoClient
603602
public func withUnsafeContinuation<T>(
603+
isolation: isolated (any Actor)? = #isolation,
604+
_ fn: (UnsafeContinuation<T, Never>) -> Void
605+
) async -> T {
606+
return await Builtin.withUnsafeContinuation {
607+
fn(UnsafeContinuation<T, Never>($0))
608+
}
609+
}
610+
611+
@available(SwiftStdlib 5.1, *)
612+
@_unsafeInheritExecutor
613+
@_alwaysEmitIntoClient
614+
public func __abi_withUnsafeContinuation<T>(
604615
_ fn: (UnsafeContinuation<T, Never>) -> Void
605616
) async -> T {
606617
return await Builtin.withUnsafeContinuation {
@@ -634,9 +645,20 @@ public func withUnsafeContinuation<T>(
634645
/// - SeeAlso: `withCheckedContinuation(function:_:)`
635646
/// - SeeAlso: `withCheckedThrowingContinuation(function:_:)`
636647
@available(SwiftStdlib 5.1, *)
637-
@_unsafeInheritExecutor
638648
@_alwaysEmitIntoClient
639649
public func withUnsafeThrowingContinuation<T>(
650+
isolation: isolated (any Actor)? = #isolation,
651+
_ fn: (UnsafeContinuation<T, Error>) -> Void
652+
) async throws -> T {
653+
return try await Builtin.withUnsafeThrowingContinuation {
654+
fn(UnsafeContinuation<T, Error>($0))
655+
}
656+
}
657+
658+
@available(SwiftStdlib 5.1, *)
659+
@_unsafeInheritExecutor
660+
@_alwaysEmitIntoClient
661+
public func __abi_withUnsafeThrowingContinuation<T>(
640662
_ fn: (UnsafeContinuation<T, Error>) -> Void
641663
) async throws -> T {
642664
return try await Builtin.withUnsafeThrowingContinuation {

stdlib/public/Concurrency/Task.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,9 +346,9 @@ static SerialExecutorRef executorForEnqueuedJob(Job *job) {
346346
return SerialExecutorRef::generic();
347347
#else
348348
void *jobQueue = job->SchedulerPrivate[Job::DispatchQueueIndex];
349-
if (jobQueue == DISPATCH_QUEUE_GLOBAL_EXECUTOR)
349+
if (jobQueue == DISPATCH_QUEUE_GLOBAL_EXECUTOR) {
350350
return SerialExecutorRef::generic();
351-
else
351+
} else
352352
return SerialExecutorRef::forOrdinary(reinterpret_cast<HeapObject*>(jobQueue),
353353
_swift_task_getDispatchQueueSerialExecutorWitnessTable());
354354
#endif

stdlib/toolchain/Compatibility56/Concurrency/Actor.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class ExecutorTrackingInfo {
6767

6868
/// Unconditionally initialize a fresh tracking state on the
6969
/// current state, shadowing any previous tracking state.
70-
/// leave() must be called beforet the object goes out of scope.
70+
/// leave() must be called before the object goes out of scope.
7171
void enterAndShadow(ExecutorRef currentExecutor) {
7272
ActiveExecutor = currentExecutor;
7373
SavedInfo = ActiveInfoInThread.get();

test/Concurrency/Runtime/continuation_validation.swift

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,72 @@
1616

1717
import StdlibUnittest
1818

19+
@MainActor
20+
@available(SwiftStdlib 5.1, *)
21+
func test_isolation_withUnsafeContinuation() async {
22+
// This test specifically should have only one suspension point,
23+
// as it would trigger a problem with the previous @_unsafeInheritExecutor
24+
// implementation, where we optimize away a switch accidentally, causing
25+
// wrong isolation.
26+
await withUnsafeContinuation { continuation in
27+
MainActor.shared.assertIsolated() // OK
28+
continuation.resume(returning: ())
29+
}
30+
}
31+
@MainActor
32+
@available(SwiftStdlib 5.1, *)
33+
func test_isolation_withUnsafeThrowingContinuation() async {
34+
// See comment in `test_isolation_withUnsafeContinuation` about exact test case shape
35+
try! await withUnsafeThrowingContinuation { continuation in
36+
MainActor.shared.assertIsolated() // OK
37+
continuation.resume(returning: ())
38+
}
39+
}
40+
@MainActor
41+
@available(SwiftStdlib 5.1, *)
42+
func test_isolation_withCheckedContinuation() async {
43+
// See comment in `test_isolation_withUnsafeContinuation` about exact test case shape
44+
await withCheckedContinuation { continuation in
45+
MainActor.shared.assertIsolated() // OK
46+
continuation.resume(returning: ())
47+
}
48+
}
49+
@MainActor
50+
@available(SwiftStdlib 5.1, *)
51+
func test_isolation_withCheckedThrowingContinuation() async {
52+
// See comment in `test_isolation_withUnsafeContinuation` about exact test case shape
53+
try! await withCheckedThrowingContinuation { continuation in
54+
MainActor.shared.assertIsolated() // OK
55+
continuation.resume(returning: ())
56+
}
57+
}
58+
1959
@main struct Main {
2060
static func main() async {
2161
let tests = TestSuite("ContinuationValidation")
2262

2363
if #available(SwiftStdlib 5.1, *) {
64+
tests.test("withUnsafeThrowingContinuation: continuation should be on calling isolation") {
65+
await Task.detached {
66+
await test_isolation_withUnsafeThrowingContinuation()
67+
}.value
68+
}
69+
tests.test("withUnsafeContinuation: continuation should be on calling isolation") {
70+
await Task.detached {
71+
await test_isolation_withUnsafeContinuation()
72+
}.value
73+
}
74+
tests.test("withCheckedContinuation: continuation should be on calling isolation") {
75+
await Task.detached {
76+
await test_isolation_withCheckedContinuation()
77+
}.value
78+
}
79+
tests.test("withCheckedThrowingContinuation: continuation should be on calling isolation") {
80+
await Task.detached {
81+
await test_isolation_withCheckedThrowingContinuation()
82+
}.value
83+
}
84+
2485
tests.test("trap on double resume of unchecked continuation") {
2586
expectCrashLater(withMessage: "may have already been resumed")
2687

0 commit comments

Comments
 (0)