mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
* Rework YieldingContinuation to service values in a buffered fashion * Fix word size calculation for locks * Handle terminal states and finished/failed storage * Wrap yielding continuation into a more featureful type for better ergonomics * Hope springs eternal, maybe windows works with this? * Prevent value overflows at .max limits * Add a cancellation handler * Fix series tests missing continuation parameters * Fix series tests for mutable itertaors * Rename to a more general name for Series's inner continuation type * Whitespace fixes and add more commentary about public functions on Series * Restore YieldingContinuation for now with deprecations to favor Series * Ensure onCancel is invoked in deinit phases, and eliminate a potential for double cancellation * Make sure ThrowingSeries has the same nonmutating setter for onCancel as Series * Add a swath of more unit tests that exersize cancellation behavior as well as throwing behaviors * Remove work-around for async testing * Fixup do/catch range to properly handle ThrowingSeries test * Address naming consistency of resume result function * Adopt the async main test setup * More migration of tests to new async mechanisms * Handle the double finish/throw case * Ensure the dependency on Dispatch is built for the series tests (due to semaphore usage) * Add import-libdispatch to run command for Series tests * Use non-combine based timeout intervals (portable to linux) for dispatch semaphore * Rename Series -> AsyncStream and resume functions to just yield, and correct a missing default Element.self value * Fix missing naming change issue for yielding an error on AsyncThrowingStream * Remove argument label of buffering from tests * Extract buffer and throwing variants into their own file * Slightly refactor for only needing to store the producer instead of producer and cancel * Rename onCancel to onTermination * Convert handler access into a function pair * Add finished states to the termination handler event pipeline and a disambiguation enum to identify finish versus cancel * Ensure all termination happens before event propigation (and outside of the locks) and warn against requirements for locking on terminate and enqueue * Modified to use Deque to back the storage and move the storage to inner types; overall perf went from 200kE/sec to over 1ME/sec * Update stdlib/public/Concurrency/AsyncStream.swift Co-authored-by: Doug Gregor <dgregor@apple.com> * Update stdlib/public/Concurrency/AsyncThrowingStream.swift Co-authored-by: Doug Gregor <dgregor@apple.com> * Update stdlib/public/Concurrency/AsyncStream.swift Co-authored-by: Joseph Heck <heckj@mac.com> * Update stdlib/public/Concurrency/AsyncThrowingStream.swift Co-authored-by: Joseph Heck <heckj@mac.com> * Update stdlib/public/Concurrency/AsyncThrowingStream.swift Co-authored-by: Joseph Heck <heckj@mac.com> * Update stdlib/public/Concurrency/AsyncThrowingStream.swift Co-authored-by: Joseph Heck <heckj@mac.com> * Update stdlib/public/Concurrency/AsyncStream.swift Co-authored-by: Joseph Heck <heckj@mac.com> * Update stdlib/public/Concurrency/AsyncThrowingStream.swift Co-authored-by: Joseph Heck <heckj@mac.com> * Remove local cruft for overlay disabling * Remove local cruft for Dispatch overlay work * Remove potential ABI impact for adding Deque Co-authored-by: Doug Gregor <dgregor@apple.com> Co-authored-by: Joseph Heck <heckj@mac.com>
189 lines
7.2 KiB
Swift
189 lines
7.2 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2020-2021 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import Swift
|
|
|
|
/// An ordered, asynchronously generated sequence of elements.
|
|
///
|
|
/// AsyncStream is an interface type to adapt from code producing values to an
|
|
/// asynchronous context iterating them. This is itended to be used to allow
|
|
/// callback or delegation based APIs to participate with async/await.
|
|
///
|
|
/// When values are produced from a non async/await source there is a
|
|
/// consideration that must be made on behavioral characteristics of how that
|
|
/// production of values interacts with the iteration. AsyncStream offers a
|
|
/// initialization strategy that provides a method of yielding values into
|
|
/// iteration.
|
|
///
|
|
/// AsyncStream can be initialized with the option to buffer to a given limit.
|
|
/// The default value for this limit is Int.max. The buffering is only for
|
|
/// values that have yet to be consumed by iteration. Values can be yielded in
|
|
/// case to the continuation passed into the build closure. That continuation
|
|
/// is Sendable, in that it is intended to be used from concurrent contexts
|
|
/// external to the iteration of the AsyncStream.
|
|
///
|
|
/// A trivial use case producing values from a detached task would work as such:
|
|
///
|
|
/// let digits = AsyncStream(Int.self) { continuation in
|
|
/// detach {
|
|
/// for digit in 0..<10 {
|
|
/// continuation.yield(digit)
|
|
/// }
|
|
/// continuation.finish()
|
|
/// }
|
|
/// }
|
|
///
|
|
/// for await digit in digits {
|
|
/// print(digit)
|
|
/// }
|
|
///
|
|
@available(SwiftStdlib 5.5, *)
|
|
public struct AsyncStream<Element> {
|
|
public struct Continuation: Sendable {
|
|
public enum Termination {
|
|
case finished
|
|
case cancelled
|
|
}
|
|
|
|
let storage: _Storage
|
|
|
|
/// Resume the task awaiting the next iteration point by having it return
|
|
/// nomally from its suspension point or buffer the value if no awaiting
|
|
/// next iteration is active.
|
|
///
|
|
/// - Parameter value: The value to yield from the continuation.
|
|
///
|
|
/// This can be called more than once and returns to the caller immediately
|
|
/// without blocking for any awaiting consuption from the iteration.
|
|
public func yield(_ value: __owned Element) {
|
|
storage.yield(value)
|
|
}
|
|
|
|
/// Resume the task awaiting the next iteration point by having it return
|
|
/// nil which signifies the end of the iteration.
|
|
///
|
|
/// Calling this function more than once is idempotent; i.e. finishing more
|
|
/// than once does not alter the state beyond the requirements of
|
|
/// AsyncSequence; which claims that all values past a terminal state are
|
|
/// nil.
|
|
public func finish() {
|
|
storage.finish()
|
|
}
|
|
|
|
/// A callback to invoke when iteration of a AsyncStream is cancelled.
|
|
///
|
|
/// If an `onTermination` callback is set, when iteration of a AsyncStream is
|
|
/// cancelled via task cancellation that callback is invoked. The callback
|
|
/// is disposed of after any terminal state is reached.
|
|
///
|
|
/// Cancelling an active iteration will first invoke the onTermination callback
|
|
/// and then resume yeilding nil. This means that any cleanup state can be
|
|
/// emitted accordingly in the cancellation handler
|
|
public var onTermination: (@Sendable (Termination) -> Void)? {
|
|
get {
|
|
return storage.getOnTermination()
|
|
}
|
|
nonmutating set {
|
|
storage.setOnTermination(newValue)
|
|
}
|
|
}
|
|
}
|
|
|
|
let produce: () async -> Element?
|
|
|
|
/// Construct a AsyncStream buffering given an Element type.
|
|
///
|
|
/// - Parameter elementType: The type the AsyncStream will produce.
|
|
/// - Parameter maxBufferedElements: The maximum number of elements to
|
|
/// hold in the buffer past any checks for continuations being resumed.
|
|
/// - Parameter build: The work associated with yielding values to the AsyncStream.
|
|
///
|
|
/// The maximum number of pending elements limited by dropping the oldest
|
|
/// value when a new value comes in if the buffer would excede the limit
|
|
/// placed upon it. By default this limit is unlimited.
|
|
///
|
|
/// The build closure passes in a Continuation which can be used in
|
|
/// concurrent contexts. It is thread safe to send and finish; all calls are
|
|
/// to the continuation are serialized, however calling this from multiple
|
|
/// concurrent contexts could result in out of order delivery.
|
|
public init(
|
|
_ elementType: Element.Type = Element.self,
|
|
maxBufferedElements limit: Int = .max,
|
|
_ build: (Continuation) -> Void
|
|
) {
|
|
let storage: _Storage = .create(limit: limit)
|
|
produce = storage.next
|
|
build(Continuation(storage: storage))
|
|
}
|
|
}
|
|
|
|
@available(SwiftStdlib 5.5, *)
|
|
extension AsyncStream: AsyncSequence {
|
|
/// The asynchronous iterator for iterating a AsyncStream.
|
|
///
|
|
/// This type is specificially not Sendable. It is not intended to be used
|
|
/// from multiple concurrent contexts. Any such case that next is invoked
|
|
/// concurrently and contends with another call to next is a programmer error
|
|
/// and will fatalError.
|
|
public struct Iterator: AsyncIteratorProtocol {
|
|
let produce: () async -> Element?
|
|
|
|
/// The next value from the AsyncStream.
|
|
///
|
|
/// When next returns nil this signifies the end of the AsyncStream. Any such
|
|
/// case that next is invoked concurrently and contends with another call to
|
|
/// next is a programmer error and will fatalError.
|
|
///
|
|
/// If the task this iterator is running in is canceled while next is
|
|
/// awaiting a value, this will terminate the AsyncStream and next may return nil
|
|
/// immediately (or will return nil on subseuqent calls)
|
|
public mutating func next() async -> Element? {
|
|
await produce()
|
|
}
|
|
}
|
|
|
|
/// Construct an iterator.
|
|
public func makeAsyncIterator() -> Iterator {
|
|
return Iterator(produce: produce)
|
|
}
|
|
}
|
|
|
|
@available(SwiftStdlib 5.5, *)
|
|
extension AsyncStream.Continuation {
|
|
/// Resume the task awaiting the next iteration point by having it return
|
|
/// normally from its suspension point or buffer the value if no awaiting
|
|
/// next iteration is active.
|
|
///
|
|
/// - Parameter result: A result to yield from the continuation.
|
|
///
|
|
/// This can be called more than once and returns to the caller immediately
|
|
/// without blocking for any awaiting consuption from the iteration.
|
|
public func yield(
|
|
with result: Result<Element, Never>
|
|
) {
|
|
switch result {
|
|
case .success(let val):
|
|
storage.yield(val)
|
|
}
|
|
}
|
|
|
|
/// Resume the task awaiting the next iteration point by having it return
|
|
/// normally from its suspension point or buffer the value if no awaiting
|
|
/// next iteration is active where the `Element` is `Void`.
|
|
///
|
|
/// This can be called more than once and returns to the caller immediately
|
|
/// without blocking for any awaiting consuption from the iteration.
|
|
public func yield() where Element == Void {
|
|
storage.yield(())
|
|
}
|
|
}
|