Skip to content

Commit eb0bd6a

Browse files
[Concurrency] YieldingContinuation (#36730)
* Implement a YieldingContinuation type suitable for emitting values more than once via a yielding family of functions and awaiting production via next * Add availability for YieldingContinuation and tests * remove UnsafeConcurrentValue * use UnsafeSendable for now * Ensure the testing contexts are actually async * Change the usages of Task.runDetached to Task.detach * Change the usages of Task.detach to detach * Transition to a external storage class outside of the generic, move to acqrel atomics, and change the error type to be enforced to Error existentials for next. * Apply suggestions from code review Co-authored-by: Nate Cook <[email protected]> * Remove inlines to allow for resilient changes * Add unreachable cases in testing Co-authored-by: Nate Cook <[email protected]>
1 parent d8582f8 commit eb0bd6a

File tree

3 files changed

+444
-0
lines changed

3 files changed

+444
-0
lines changed

stdlib/public/Concurrency/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ add_swift_target_library(swift_Concurrency ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} I
6969
TaskLocal.swift
7070
ThreadSanitizer.cpp
7171
Mutex.cpp
72+
YieldingContinuation.swift
7273
${swift_concurrency_objc_sources}
7374

7475
SWIFT_MODULE_DEPENDS_LINUX Glibc
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2020-2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Swift
14+
15+
internal final class _YieldingContinuationStorage: UnsafeSendable {
16+
var continuation: Builtin.RawUnsafeContinuation?
17+
}
18+
19+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
20+
public struct YieldingContinuation<Element, Failure: Error>: Sendable {
21+
let storage = _YieldingContinuationStorage()
22+
23+
/// Construct a YieldingContinuation.
24+
///
25+
/// This continuation type can be called more than once, unlike the unsafe and
26+
/// checked counterparts. Each call to the yielding functions will resume any
27+
/// awaiter on the next function. This type is inherently sendable and can
28+
/// safely be used and stored in multiple task contexts.
29+
public init() { }
30+
31+
/// Construct a YieldingContinuation with specific types including a failure.
32+
///
33+
/// This continuation type can be called more than once, unlike the unsafe and
34+
/// checked counterparts. Each call to the yielding functions will resume any
35+
/// awaiter on the next function. This type is inherently sendable and can
36+
/// safely be used and stored in multiple task contexts.
37+
public init(yielding: Element.Type, throwing: Failure.Type) { }
38+
39+
internal func _extract() -> UnsafeContinuation<Element, Error>? {
40+
let raw = Builtin.atomicrmw_xchg_acqrel_Word(
41+
Builtin.addressof(&storage.continuation),
42+
UInt(bitPattern: 0)._builtinWordValue)
43+
return unsafeBitCast(raw, to: UnsafeContinuation<Element, Error>?.self)
44+
}
45+
46+
internal func _inject(
47+
_ continuation: UnsafeContinuation<Element, Error>
48+
) -> UnsafeContinuation<Element, Error>? {
49+
let rawContinuation = unsafeBitCast(continuation, to: Builtin.Word.self)
50+
let raw = Builtin.atomicrmw_xchg_acqrel_Word(
51+
Builtin.addressof(&storage.continuation), rawContinuation)
52+
return unsafeBitCast(raw, to: UnsafeContinuation<Element, Error>?.self)
53+
}
54+
55+
/// Resume the task awaiting next by having it return normally from its
56+
/// suspension point.
57+
///
58+
/// - Parameter value: The value to return from an awaiting call to next.
59+
///
60+
/// Unlike other continuations `YieldingContinuation` may resume more than
61+
/// once. However if there are no potential awaiting calls to `next` this
62+
/// function will return false, indicating that the caller needs to decide how
63+
/// the behavior should be handled.
64+
public func yield(_ value: __owned Element) -> Bool {
65+
if let continuation = _extract() {
66+
continuation.resume(returning: value)
67+
return true
68+
}
69+
return false
70+
}
71+
72+
/// Resume the task awaiting the continuation by having it throw an error
73+
/// from its suspension point.
74+
///
75+
/// - Parameter error: The error to throw from an awaiting call to next.
76+
///
77+
/// Unlike other continuations `YieldingContinuation` may resume more than
78+
/// once. However if there are no potential awaiting calls to `next` this
79+
/// function will return false, indicating that the caller needs to decide how
80+
/// the behavior should be handled.
81+
public func yield(throwing error: __owned Failure) -> Bool {
82+
if let continuation = _extract() {
83+
continuation.resume(throwing: error)
84+
return true
85+
}
86+
return false
87+
}
88+
}
89+
90+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
91+
extension YieldingContinuation where Failure == Error {
92+
/// Await a resume from a call to a yielding function.
93+
///
94+
/// - Return: The element that was yielded or a error that was thrown.
95+
///
96+
/// When multiple calls are awaiting a produced value from next any call to
97+
/// yield will resume all awaiting calls to next with that value.
98+
public func next() async throws -> Element {
99+
var existing: UnsafeContinuation<Element, Error>?
100+
do {
101+
let result = try await withUnsafeThrowingContinuation {
102+
(continuation: UnsafeContinuation<Element, Error>) in
103+
existing = _inject(continuation)
104+
}
105+
existing?.resume(returning: result)
106+
return result
107+
} catch {
108+
existing?.resume(throwing: error)
109+
throw error
110+
}
111+
}
112+
}
113+
114+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
115+
extension YieldingContinuation where Failure == Never {
116+
/// Construct a YieldingContinuation with a specific Element type.
117+
///
118+
/// This continuation type can be called more than once, unlike the unsafe and
119+
/// checked counterparts. Each call to the yielding functions will resume any
120+
/// awaiter on the next function. This type is inherently sendable and can
121+
/// safely be used and stored in multiple task contexts.
122+
public init(yielding: Element.Type) { }
123+
124+
/// Await a resume from a call to a yielding function.
125+
///
126+
/// - Return: The element that was yielded.
127+
public func next() async -> Element {
128+
var existing: UnsafeContinuation<Element, Error>?
129+
let result = try! await withUnsafeThrowingContinuation {
130+
(continuation: UnsafeContinuation<Element, Error>) in
131+
existing = _inject(continuation)
132+
}
133+
existing?.resume(returning: result)
134+
return result
135+
}
136+
}
137+
138+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
139+
extension YieldingContinuation {
140+
/// Resume the task awaiting the continuation by having it either
141+
/// return normally or throw an error based on the state of the given
142+
/// `Result` value.
143+
///
144+
/// - Parameter result: A value to either return or throw from the
145+
/// continuation.
146+
///
147+
/// Unlike other continuations `YieldingContinuation` may resume more than
148+
/// once. However if there are no potential awaiting calls to `next` this
149+
/// function will return false, indicating that the caller needs to decide how
150+
/// the behavior should be handled.
151+
public func yield<Er: Error>(
152+
with result: Result<Element, Er>
153+
) -> Bool where Failure == Error {
154+
switch result {
155+
case .success(let val):
156+
return self.yield(val)
157+
case .failure(let err):
158+
return self.yield(throwing: err)
159+
}
160+
}
161+
162+
/// Resume the task awaiting the continuation by having it either
163+
/// return normally or throw an error based on the state of the given
164+
/// `Result` value.
165+
///
166+
/// - Parameter result: A value to either return or throw from the
167+
/// continuation.
168+
///
169+
/// Unlike other continuations `YieldingContinuation` may resume more than
170+
/// once. However if there are no potential awaiting calls to `next` this
171+
/// function will return false, indicating that the caller needs to decide how
172+
/// the behavior should be handled.
173+
public func yield(with result: Result<Element, Failure>) -> Bool {
174+
switch result {
175+
case .success(let val):
176+
return self.yield(val)
177+
case .failure(let err):
178+
return self.yield(throwing: err)
179+
}
180+
}
181+
182+
/// Resume the task awaiting the continuation by having it return normally
183+
/// from its suspension point.
184+
///
185+
/// Unlike other continuations `YieldingContinuation` may resume more than
186+
/// once. However if there are no potential awaiting calls to `next` this
187+
/// function will return false, indicating that the caller needs to decide how
188+
/// the behavior should be handled.
189+
public func yield() -> Bool where Element == Void {
190+
return self.yield(())
191+
}
192+
}
193+

0 commit comments

Comments
 (0)