Skip to content

[6.0][concurrency] Update withUnsafe{,Throwing}Continuation to have a sending result. #74163

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions stdlib/public/Concurrency/PartialAsyncTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ public struct UnsafeContinuation<T, E: Error>: Sendable {
/// The task continues executing
/// when its executor schedules it.
@_alwaysEmitIntoClient
public func resume(returning value: __owned T) where E == Never {
public func resume(returning value: sending T) where E == Never {
#if compiler(>=5.5) && $BuiltinContinuation
Builtin.resumeNonThrowingContinuationReturning(context, value)
#else
Expand All @@ -527,7 +527,7 @@ public struct UnsafeContinuation<T, E: Error>: Sendable {
/// The task continues executing
/// when its executor schedules it.
@_alwaysEmitIntoClient
public func resume(returning value: __owned T) {
public func resume(returning value: sending T) {
#if compiler(>=5.5) && $BuiltinContinuation
Builtin.resumeThrowingContinuationReturning(context, value)
#else
Expand All @@ -549,7 +549,7 @@ public struct UnsafeContinuation<T, E: Error>: Sendable {
/// The task continues executing
/// when its executor schedules it.
@_alwaysEmitIntoClient
public func resume(throwing error: __owned E) {
public func resume(throwing error: consuming E) {
#if compiler(>=5.5) && $BuiltinContinuation
Builtin.resumeThrowingContinuationThrowing(context, error)
#else
Expand Down Expand Up @@ -577,7 +577,7 @@ extension UnsafeContinuation {
/// The task continues executing
/// when its executor schedules it.
@_alwaysEmitIntoClient
public func resume<Er: Error>(with result: Result<T, Er>) where E == Error {
public func resume<Er: Error>(with result: __shared sending Result<T, Er>) where E == Error {
switch result {
case .success(let val):
self.resume(returning: val)
Expand All @@ -603,7 +603,7 @@ extension UnsafeContinuation {
/// The task continues executing
/// when its executor schedules it.
@_alwaysEmitIntoClient
public func resume(with result: Result<T, E>) {
public func resume(with result: __shared sending Result<T, E>) {
switch result {
case .success(let val):
self.resume(returning: val)
Expand Down Expand Up @@ -635,7 +635,7 @@ extension UnsafeContinuation {
@_alwaysEmitIntoClient
internal func _resumeUnsafeContinuation<T>(
_ continuation: UnsafeContinuation<T, Never>,
_ value: __owned T
_ value: sending T
) {
continuation.resume(returning: value)
}
Expand All @@ -644,7 +644,7 @@ internal func _resumeUnsafeContinuation<T>(
@_alwaysEmitIntoClient
internal func _resumeUnsafeThrowingContinuation<T>(
_ continuation: UnsafeContinuation<T, Error>,
_ value: __owned T
_ value: sending T
) {
continuation.resume(returning: value)
}
Expand All @@ -653,7 +653,7 @@ internal func _resumeUnsafeThrowingContinuation<T>(
@_alwaysEmitIntoClient
internal func _resumeUnsafeThrowingContinuationWithError<T>(
_ continuation: UnsafeContinuation<T, Error>,
_ error: __owned Error
_ error: consuming Error
) {
continuation.resume(throwing: error)
}
Expand Down Expand Up @@ -689,7 +689,7 @@ internal func _resumeUnsafeThrowingContinuationWithError<T>(
public func withUnsafeContinuation<T>(
isolation: isolated (any Actor)? = #isolation,
_ fn: (UnsafeContinuation<T, Never>) -> Void
) async -> T {
) async -> sending T {
return await Builtin.withUnsafeContinuation {
fn(UnsafeContinuation<T, Never>($0))
}
Expand Down Expand Up @@ -725,7 +725,7 @@ public func withUnsafeContinuation<T>(
public func withUnsafeThrowingContinuation<T>(
isolation: isolated (any Actor)? = #isolation,
_ fn: (UnsafeContinuation<T, Error>) -> Void
) async throws -> T {
) async throws -> sending T {
return try await Builtin.withUnsafeThrowingContinuation {
fn(UnsafeContinuation<T, Error>($0))
}
Expand Down
128 changes: 128 additions & 0 deletions test/Concurrency/sending_continuation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ func withCheckedContinuation_1() async -> NonSendableKlass {
}
}

func withCheckedContinuation_1a() async -> NonSendableKlass {
await withCheckedContinuation { continuation in
continuation.resume(returning: NonSendableKlass())
}
}

@MainActor
func withCheckedContinuation_2() async -> NonSendableKlass {
await withCheckedContinuation { continuation in
Expand All @@ -34,6 +40,16 @@ func withCheckedContinuation_2() async -> NonSendableKlass {
}
}

func withCheckedContinuation_2a() async -> NonSendableKlass {
await withCheckedContinuation { continuation in
let x = NonSendableKlass()
continuation.resume(returning: x)
// expected-error @-1 {{sending 'x' risks causing data races}}
// expected-note @-2 {{'x' used after being passed as a 'sending' parameter}}
useValue(x) // expected-note {{access can happen concurrently}}
}
}

@MainActor
func withCheckedContinuation_3() async {
// x is main actor isolated since withCheckedContinuation is #isolated.
Expand All @@ -49,6 +65,19 @@ func withCheckedContinuation_3() async {
// expected-note @-2 {{sending main actor-isolated 'x' to nonisolated global function 'useValueAsync' risks causing data races between nonisolated and main actor-isolated uses}}
}

func withCheckedContinuation_3a() async {
let x = await withCheckedContinuation { continuation in
let x = NonSendableKlass()
continuation.resume(returning: x)
// expected-error @-1 {{sending 'x' risks causing data races}}
// expected-note @-2 {{'x' used after being passed as a 'sending' parameter}}
useValue(x) // expected-note {{access can happen concurrently}}
}

// This is ok since x is disconnected.
await useValueAsync(x)
}

@MainActor
func withCheckedContinuation_4() async {
// x is main actor isolated since withCheckedContinuation is #isolated.
Expand All @@ -64,6 +93,18 @@ func withCheckedContinuation_4() async {
// expected-note @-2 {{sending main actor-isolated 'x' to nonisolated global function 'useValueAsync' risks causing data races between nonisolated and main actor-isolated uses}}
}

func withCheckedContinuation_4a() async {
// x is main actor isolated since withCheckedContinuation is #isolated.
let y = NonSendableKlass()
let x = await withCheckedContinuation { continuation in
continuation.resume(returning: y)
// expected-error @-1 {{sending 'y' risks causing data races}}
// expected-note @-2 {{task-isolated 'y' is passed as a 'sending' parameter}}
useValue(y)
}
await useValueAsync(x)
}

@MainActor func testAsyncStream() {
let (_, continuation) = AsyncStream.makeStream(of: NonSendableKlass.self)

Expand Down Expand Up @@ -103,3 +144,90 @@ func withCheckedContinuation_4() async {
useValue(x) // expected-note {{access can happen concurrently}}
}
}

@MainActor
func withUnsafeContinuation_1() async -> NonSendableKlass {
await withUnsafeContinuation { continuation in
continuation.resume(returning: NonSendableKlass())
}
}

func withUnsafeContinuation_1a() async -> NonSendableKlass {
await withUnsafeContinuation { continuation in
continuation.resume(returning: NonSendableKlass())
}
}

@MainActor
func withUnsafeContinuation_2() async -> NonSendableKlass {
await withUnsafeContinuation { continuation in
let x = NonSendableKlass()
continuation.resume(returning: x)
// expected-error @-1 {{sending 'x' risks causing data races}}
// expected-note @-2 {{'x' used after being passed as a 'sending' parameter}}
useValue(x) // expected-note {{access can happen concurrently}}
}
}

func withUnsafeContinuation_2a() async -> NonSendableKlass {
await withUnsafeContinuation { continuation in
let x = NonSendableKlass()
continuation.resume(returning: x)
// expected-error @-1 {{sending 'x' risks causing data races}}
// expected-note @-2 {{'x' used after being passed as a 'sending' parameter}}
useValue(x) // expected-note {{access can happen concurrently}}
}
}

@MainActor
func withUnsafeContinuation_3() async {
// x is main actor isolated since withUnsafeContinuation is #isolated.
let x = await withUnsafeContinuation { continuation in
let x = NonSendableKlass()
continuation.resume(returning: x)
// expected-error @-1 {{sending 'x' risks causing data races}}
// expected-note @-2 {{'x' used after being passed as a 'sending' parameter}}
useValue(x) // expected-note {{access can happen concurrently}}
}
await useValueAsync(x)
// expected-error @-1 {{sending 'x' risks causing data races}}
// expected-note @-2 {{sending main actor-isolated 'x' to nonisolated global function 'useValueAsync' risks causing data races between nonisolated and main actor-isolated uses}}
}

func withUnsafeContinuation_3a() async {
let x = await withUnsafeContinuation { continuation in
let x = NonSendableKlass()
continuation.resume(returning: x)
// expected-error @-1 {{sending 'x' risks causing data races}}
// expected-note @-2 {{'x' used after being passed as a 'sending' parameter}}
useValue(x) // expected-note {{access can happen concurrently}}
}
await useValueAsync(x)
}

@MainActor
func withUnsafeContinuation_4() async {
// x is main actor isolated since withUnsafeContinuation is #isolated.
let y = NonSendableKlass()
let x = await withUnsafeContinuation { continuation in
continuation.resume(returning: y)
// expected-error @-1 {{sending 'y' risks causing data races}}
// expected-note @-2 {{main actor-isolated 'y' is passed as a 'sending' parameter}}
useValue(y)
}
await useValueAsync(x)
// expected-error @-1 {{sending 'x' risks causing data races}}
// expected-note @-2 {{sending main actor-isolated 'x' to nonisolated global function 'useValueAsync' risks causing data races between nonisolated and main actor-isolated uses}}
}

func withUnsafeContinuation_4a() async {
// x is main actor isolated since withUnsafeContinuation is #isolated.
let y = NonSendableKlass()
let x = await withUnsafeContinuation { continuation in
continuation.resume(returning: y)
// expected-error @-1 {{sending 'y' risks causing data races}}
// expected-note @-2 {{task-isolated 'y' is passed as a 'sending' parameter}}
useValue(y)
}
await useValueAsync(x)
}