From c8dc3fd019a83797a79d8b0e1f6bdf71b7702942 Mon Sep 17 00:00:00 2001 From: Soumya Ranjan Mahunt Date: Sun, 4 Sep 2022 22:29:54 +0530 Subject: [PATCH 01/14] refactor(`AsyncObject`)!: propagate cancellation error instead of swallowing --- .../AsyncObjects/AsyncCountdownEvent.swift | 73 +++++-- Sources/AsyncObjects/AsyncEvent.swift | 46 ++++- Sources/AsyncObjects/AsyncObject.swift | 176 ++++++++++------- Sources/AsyncObjects/AsyncSemaphore.swift | 40 ++-- Sources/AsyncObjects/CancellationSource.swift | 183 +++++------------- Sources/AsyncObjects/TaskOperation.swift | 22 ++- Sources/AsyncObjects/TaskQueue.swift | 10 +- .../AsyncCountdownEventTests.swift | 148 ++++++++------ Tests/AsyncObjectsTests/AsyncEventTests.swift | 65 ++++--- .../AsyncObjectsTests/AsyncObjectTests.swift | 96 ++++----- .../AsyncSemaphoreTests.swift | 90 +++++---- .../CancellationSourceTests.swift | 83 ++++---- .../TaskOperationTests.swift | 61 +++--- Tests/AsyncObjectsTests/TaskQueueTests.swift | 65 +++++-- Tests/AsyncObjectsTests/XCTestCase.swift | 5 +- 15 files changed, 651 insertions(+), 512 deletions(-) diff --git a/Sources/AsyncObjects/AsyncCountdownEvent.swift b/Sources/AsyncObjects/AsyncCountdownEvent.swift index 2f7795ad..4c30eae5 100644 --- a/Sources/AsyncObjects/AsyncCountdownEvent.swift +++ b/Sources/AsyncObjects/AsyncCountdownEvent.swift @@ -15,7 +15,22 @@ import OrderedCollections /// You can indicate high priority usage of resource by using ``increment(by:)`` method, /// and indicate free of resource by calling ``signal(repeat:)`` or ``signal()`` methods. /// For low priority resource usage or detect resource idling use ``wait()`` method -/// or its timeout variation ``wait(forNanoseconds:)``. +/// or its timeout variation ``wait(forNanoseconds:)``: +/// +/// ```swift +/// // create event with initial count and count down limit +/// let event = AsyncCountdownEvent() +/// // increment countdown count from high priority tasks +/// event.increment(by: 1) +/// +/// // wait for countdown signal from low priority tasks, fails only if task cancelled +/// try await event.wait() +/// // or wait with some timeout +/// try await event.wait(forNanoseconds: 1_000_000_000) +/// +/// // signal countdown after completing high priority tasks +/// event.signal() +/// ``` /// /// Use the ``limit`` parameter to indicate concurrent low priority usage, i.e. if limit set to zero, /// only one low priority usage allowed at one time. @@ -42,7 +57,7 @@ public actor AsyncCountdownEvent: AsyncObject { /// /// Can be changed after initialization /// by using ``reset(to:)`` method. - public private(set) var initialCount: UInt + public var initialCount: UInt /// Indicates whether countdown event current count is within ``limit``. /// /// Queued tasks are resumed from suspension when event is set and until current count exceeds limit. @@ -130,6 +145,31 @@ public actor AsyncCountdownEvent: AsyncObject { } } + /// Increments the countdown event current count by the specified value. + /// + /// - Parameter count: The value by which to increase ``currentCount``. + @inlinable + func _increment(by count: UInt = 1) { + self.currentCount += count + } + + /// Resets current count to initial count. + @inlinable + func _reset() { + self.currentCount = initialCount + _resumeContinuations() + } + + /// Resets initial count and current count to specified value. + /// + /// - Parameter count: The new initial count. + @inlinable + func _reset(to count: UInt) { + initialCount = count + self.currentCount = count + _resumeContinuations() + } + // MARK: Public /// Creates new countdown event with the limit count down up to and an initial count. @@ -158,17 +198,16 @@ public actor AsyncCountdownEvent: AsyncObject { /// Use this to indicate usage of resource from high priority tasks. /// /// - Parameter count: The value by which to increase ``currentCount``. - public func increment(by count: UInt = 1) { - self.currentCount += count + public nonisolated func increment(by count: UInt = 1) { + Task { await _increment(by: count) } } /// Resets current count to initial count. /// /// If the current count becomes less or equal to limit, multiple queued tasks /// are resumed from suspension until current count exceeds limit. - public func reset() { - self.currentCount = initialCount - _resumeContinuations() + public nonisolated func reset() { + Task { await _reset() } } /// Resets initial count and current count to specified value. @@ -177,18 +216,16 @@ public actor AsyncCountdownEvent: AsyncObject { /// are resumed from suspension until current count exceeds limit. /// /// - Parameter count: The new initial count. - public func reset(to count: UInt) { - initialCount = count - self.currentCount = count - _resumeContinuations() + public nonisolated func reset(to count: UInt) { + Task { await _reset(to: count) } } /// Registers a signal (decrements) with the countdown event. /// /// Decrement the countdown. If the current count becomes less or equal to limit, /// one queued task is resumed from suspension. - public func signal() { - signal(repeat: 1) + public nonisolated func signal() { + Task { await _decrementCount(by: 1) } } /// Registers multiple signals (decrements by provided count) with the countdown event. @@ -197,8 +234,8 @@ public actor AsyncCountdownEvent: AsyncObject { /// multiple queued tasks are resumed from suspension until current count exceeds limit. /// /// - Parameter count: The number of signals to register. - public func signal(repeat count: UInt) { - _decrementCount(by: count) + public nonisolated func signal(repeat count: UInt) { + Task { await _decrementCount(by: count) } } /// Waits for, or increments, a countdown event. @@ -207,9 +244,11 @@ public actor AsyncCountdownEvent: AsyncObject { /// Otherwise, current task is suspended until either a signal occurs or event is reset. /// /// Use this to wait for high priority tasks completion to start low priority ones. + /// + /// - Throws: `CancellationError` if cancelled. @Sendable - public func wait() async { + public func wait() async throws { guard _wait() else { currentCount += 1; return } - try? await _withPromisedContinuation() + try await _withPromisedContinuation() } } diff --git a/Sources/AsyncObjects/AsyncEvent.swift b/Sources/AsyncObjects/AsyncEvent.swift index 966baf90..60d917a2 100644 --- a/Sources/AsyncObjects/AsyncEvent.swift +++ b/Sources/AsyncObjects/AsyncEvent.swift @@ -9,7 +9,19 @@ import Foundation /// An async event suspends tasks if current state is non-signaled and resumes execution when event is signalled. /// /// You can signal event by calling the ``signal()`` method and reset signal by calling ``reset()``. -/// Wait for event signal by calling ``wait()`` method or its timeout variation ``wait(forNanoseconds:)``. +/// Wait for event signal by calling ``wait()`` method or its timeout variation ``wait(forNanoseconds:)``: +/// +/// ```swift +/// // create event with initial state (signalled or not) +/// let event = AsyncEvent(signaledInitially: false) +/// // wait for event to be signalled, fails only if task cancelled +/// try await event.wait() +/// // or wait with some timeout +/// try await event.wait(forNanoseconds: 1_000_000_000) +/// +/// // signal event after completing some task +/// event.signal() +/// ``` public actor AsyncEvent: AsyncObject { /// The suspended tasks continuation type. @usableFromInline @@ -72,13 +84,27 @@ public actor AsyncEvent: AsyncObject { } } + /// Resets signal of event. + @inlinable + func _reset() { + signalled = false + } + + /// Signals the event and resumes all the tasks + /// suspended and waiting for signal. + @inlinable + func _signal() { + continuations.forEach { $0.value.resume() } + continuations = [:] + signalled = true + } + // MARK: Public /// Creates a new event with signal state provided. /// By default, event is initially in signalled state. /// /// - Parameter signalled: The signal state for event. - /// /// - Returns: The newly created event. public init(signaledInitially signalled: Bool = true) { self.signalled = signalled @@ -89,26 +115,26 @@ public actor AsyncEvent: AsyncObject { /// Resets signal of event. /// /// After reset, tasks have to wait for event signal to complete. - public func reset() { - signalled = false + public nonisolated func reset() { + Task { await _reset() } } /// Signals the event. /// /// Resumes all the tasks suspended and waiting for signal. - public func signal() { - continuations.forEach { $0.value.resume() } - continuations = [:] - signalled = true + public nonisolated func signal() { + Task { await _signal() } } /// Waits for event signal, or proceeds if already signalled. /// /// Only waits asynchronously, if event is in non-signaled state, /// until event is signalled. + /// + /// - Throws: `CancellationError` if cancelled. @Sendable - public func wait() async { + public func wait() async throws { guard !signalled else { return } - try? await _withPromisedContinuation() + try await _withPromisedContinuation() } } diff --git a/Sources/AsyncObjects/AsyncObject.swift b/Sources/AsyncObjects/AsyncObject.swift index 6b5eeed8..75a33bdf 100644 --- a/Sources/AsyncObjects/AsyncObject.swift +++ b/Sources/AsyncObjects/AsyncObject.swift @@ -1,51 +1,29 @@ -import Foundation - -/// A result value indicating whether a task finished before a specified time. -@frozen -public enum TaskTimeoutResult: Hashable, Sendable { - /// Indicates that a task successfully finished - /// before the specified time elapsed. - case success - /// Indicates that a task failed to finish - /// before the specified time elapsed. - case timedOut -} - /// An object type that can provide synchronization across multiple task contexts /// /// Waiting asynchronously can be done by calling ``wait()`` method, /// while object decides when to resume task. Similarly, ``signal()`` can be used /// to indicate resuming suspended tasks. +@rethrows public protocol AsyncObject: Sendable { /// Signals the object for task synchronization. /// /// Object might resume suspended tasks /// or synchronize tasks differently. @Sendable - func signal() async + func signal() /// Waits for the object to green light task execution. /// /// Waits asynchronously suspending current task, instead of blocking any thread. /// Async object has to resume the task at a later time depending on its requirement. /// + /// - Throws: `CancellationError` if cancelled. /// - Note: Method might return immediately depending upon the synchronization object requirement. @Sendable - func wait() async - /// Waits for the object to green light task execution within the duration. - /// - /// Waits asynchronously suspending current task, instead of blocking any thread within the duration. - /// Async object has to resume the task at a later time depending on its requirement. - /// Depending upon whether wait succeeds or timeout expires result is returned. - /// - /// - Parameter duration: The duration in nano seconds to wait until. - /// - Returns: The result indicating whether wait completed or timed out. - /// - Note: Method might return immediately depending upon the synchronization object requirement. - @discardableResult - @Sendable - func wait(forNanoseconds duration: UInt64) async -> TaskTimeoutResult + func wait() async throws } -public extension AsyncObject where Self: AnyObject { +// TODO: add clock based timeout for Swift >=5.7 +public extension AsyncObject { /// Waits for the object to green light task execution within the duration. /// /// Waits asynchronously suspending current task, instead of blocking any thread. @@ -53,16 +31,13 @@ public extension AsyncObject where Self: AnyObject { /// Async object has to resume the task at a later time depending on its requirement. /// /// - Parameter duration: The duration in nano seconds to wait until. - /// - Returns: The result indicating whether wait completed or timed out. + /// - Throws: `CancellationError` if cancelled or `DurationTimeoutError` if timed out. /// - Note: Method might return immediately depending upon the synchronization object requirement. - @discardableResult @Sendable - func wait(forNanoseconds duration: UInt64) async -> TaskTimeoutResult { - return await waitForTaskCompletion( + func wait(forNanoseconds duration: UInt64) async throws { + return try await waitForTaskCompletion( withTimeoutInNanoseconds: duration - ) { [weak self] in - await self?.wait() - } + ) { try await self.wait() } } } @@ -72,12 +47,13 @@ public extension AsyncObject where Self: AnyObject { /// and returns only when all the invocation completes. /// /// - Parameter objects: The objects to wait for. +/// - Throws: `CancellationError` if cancelled. @inlinable @Sendable -public func waitForAll(_ objects: [any AsyncObject]) async { - await withTaskGroup(of: Void.self) { group in +public func waitForAll(_ objects: [any AsyncObject]) async throws { + try await withThrowingTaskGroup(of: Void.self) { group in objects.forEach { group.addTask(operation: $0.wait) } - await group.waitForAll() + try await group.waitForAll() } } @@ -87,10 +63,11 @@ public func waitForAll(_ objects: [any AsyncObject]) async { /// and returns only when all the invocation completes. /// /// - Parameter objects: The objects to wait for. +/// - Throws: `CancellationError` if cancelled. @inlinable @Sendable -public func waitForAll(_ objects: any AsyncObject...) async { - await waitForAll(objects) +public func waitForAll(_ objects: any AsyncObject...) async throws { + try await waitForAll(objects) } /// Waits for multiple objects to green light task execution @@ -103,15 +80,17 @@ public func waitForAll(_ objects: any AsyncObject...) async { /// - Parameters: /// - objects: The objects to wait for. /// - duration: The duration in nano seconds to wait until. -/// - Returns: The result indicating whether wait completed or timed out. +/// +/// - Throws: `CancellationError` if cancelled +/// or `DurationTimeoutError` if timed out. @inlinable @Sendable public func waitForAll( _ objects: [any AsyncObject], forNanoseconds duration: UInt64 -) async -> TaskTimeoutResult { - return await waitForTaskCompletion(withTimeoutInNanoseconds: duration) { - await waitForAll(objects) +) async throws { + return try await waitForTaskCompletion(withTimeoutInNanoseconds: duration) { + try await waitForAll(objects) } } @@ -125,14 +104,16 @@ public func waitForAll( /// - Parameters: /// - objects: The objects to wait for. /// - duration: The duration in nano seconds to wait until. -/// - Returns: The result indicating whether wait completed or timed out. +/// +/// - Throws: `CancellationError` if cancelled +/// or `DurationTimeoutError` if timed out. @inlinable @Sendable public func waitForAll( _ objects: any AsyncObject..., forNanoseconds duration: UInt64 -) async -> TaskTimeoutResult { - return await waitForAll(objects, forNanoseconds: duration) +) async throws { + return try await waitForAll(objects, forNanoseconds: duration) } /// Waits for multiple objects to green light task execution @@ -144,12 +125,17 @@ public func waitForAll( /// - Parameters: /// - objects: The objects to wait for. /// - count: The number of objects to wait for. +/// +/// - Throws: `CancellationError` if cancelled. @inlinable @Sendable -public func waitForAny(_ objects: [any AsyncObject], count: Int = 1) async { - await withTaskGroup(of: Void.self) { group in +public func waitForAny( + _ objects: [any AsyncObject], + count: Int = 1 +) async throws { + try await withThrowingTaskGroup(of: Void.self) { group in objects.forEach { group.addTask(operation: $0.wait) } - for _ in 0.. TaskTimeoutResult { - return await waitForTaskCompletion(withTimeoutInNanoseconds: duration) { - await waitForAny(objects, count: count) +) async throws { + return try await waitForTaskCompletion(withTimeoutInNanoseconds: duration) { + try await waitForAny(objects, count: count) } } @@ -204,15 +197,17 @@ public func waitForAny( /// - objects: The objects to wait for. /// - count: The number of objects to wait for. /// - duration: The duration in nano seconds to wait until. -/// - Returns: The result indicating whether wait completed or timed out. +/// +/// - Throws: `CancellationError` if cancelled +/// or `DurationTimeoutError` if timed out. @inlinable @Sendable public func waitForAny( _ objects: any AsyncObject..., count: Int = 1, forNanoseconds duration: UInt64 -) async -> TaskTimeoutResult { - return await waitForAny(objects, count: count, forNanoseconds: duration) +) async throws { + return try await waitForAny(objects, count: count, forNanoseconds: duration) } /// Waits for the provided task to be completed within the timeout duration. @@ -223,30 +218,65 @@ public func waitForAny( /// - Parameters: /// - task: The task to execute and wait for completion. /// - timeout: The duration in nano seconds to wait until. -/// - Returns: The result indicating whether task execution completed -/// or timed out. +/// +/// - Throws: `CancellationError` if cancelled +/// or `DurationTimeoutError` if timed out. @Sendable public func waitForTaskCompletion( withTimeoutInNanoseconds timeout: UInt64, - _ task: @escaping @Sendable () async -> Void -) async -> TaskTimeoutResult { - var timedOut = true - await withTaskGroup(of: Bool.self) { group in + _ task: @escaping @Sendable () async throws -> Void +) async throws { + try await withThrowingTaskGroup(of: Void.self) { group in await GlobalContinuation.with { continuation in group.addTask { continuation.resume() - await task() - return !Task.isCancelled + try await task() } } group.addTask { await Task.yield() - return (try? await Task.sleep(nanoseconds: timeout + 1_000)) == nil - } - if let result = await group.next() { - timedOut = !result + try await Task.sleep(nanoseconds: timeout + 1_000) + throw DurationTimeoutError(for: timeout, tolerance: 1_000) } - group.cancelAll() + defer { group.cancelAll() } + try await group.next() + } +} + +/// An error that indicates a task was timed out for provided duration +/// and task specific tolerance. +/// +/// This error is also thrown automatically by +/// ``waitForTaskCompletion(withTimeoutInNanoseconds:_:)``, +/// if the task execution exceeds provided time out duration. +/// +/// While ``duration`` is user configurable, ``tolerance`` is task specific. +@frozen +public struct DurationTimeoutError: Error, Sendable { + /// The duration in nano seconds that was provided for timeout operation. + /// + /// The total timeout duration takes consideration + /// of both provided duration and operation specific tolerance: + /// + /// total timeout duration = ``duration`` + ``tolerance`` + public let duration: UInt64 + /// The additional tolerance in nano seconds used for timeout operation. + /// + /// The total timeout duration takes consideration + /// of both provided duration and operation specific tolerance: + /// + /// total timeout duration = ``duration`` + ``tolerance`` + public let tolerance: UInt64 + + /// Creates a new timeout error based on provided duration and task specific tolerance. + /// + /// - Parameters: + /// - duration: The provided timeout duration in nano seconds. + /// - tolerance: The task specific additional margin in nano seconds. + /// + /// - Returns: The newly created timeout error. + public init(for duration: UInt64, tolerance: UInt64 = 0) { + self.duration = duration + self.tolerance = tolerance } - return timedOut ? .timedOut : .success } diff --git a/Sources/AsyncObjects/AsyncSemaphore.swift b/Sources/AsyncObjects/AsyncSemaphore.swift index de3278db..bc580c8d 100644 --- a/Sources/AsyncObjects/AsyncSemaphore.swift +++ b/Sources/AsyncObjects/AsyncSemaphore.swift @@ -12,7 +12,18 @@ import OrderedCollections /// /// You increment a semaphore count by calling the ``signal()`` method /// and decrement a semaphore count by calling ``wait()`` method -/// or its timeout variation ``wait(forNanoseconds:)``. +/// or its timeout variation ``wait(forNanoseconds:)``: +/// +/// ```swift +/// // create limiting concurrent access count +/// let semaphore = AsyncSemaphore(value: 1) +/// // wait for semaphore access, fails only if task cancelled +/// try await semaphore.wait() +/// // or wait with some timeout +/// try await semaphore.wait(forNanoseconds: 1_000_000_000) +/// // release after executing critical async tasks +/// defer { semaphore.signal() } +/// ``` public actor AsyncSemaphore: AsyncObject { /// The suspended tasks continuation type. @usableFromInline @@ -89,6 +100,15 @@ public actor AsyncSemaphore: AsyncObject { } } + /// Signals (increments) and releases a semaphore. + @inlinable + func _signal() { + _incrementCount() + guard !continuations.isEmpty else { return } + let (_, continuation) = continuations.removeFirst() + continuation.resume() + } + // MARK: Public /// Creates new counting semaphore with an initial value. @@ -98,7 +118,6 @@ public actor AsyncSemaphore: AsyncObject { /// Passing a value greater than zero is useful for managing a finite pool of resources, where the pool size is equal to the value. /// /// - Parameter count: The starting value for the semaphore. - /// /// - Returns: The newly created semaphore. public init(value count: UInt = 0) { self.limit = count + 1 @@ -110,22 +129,21 @@ public actor AsyncSemaphore: AsyncObject { /// Signals (increments) a semaphore. /// /// Increment the counting semaphore. - /// If the previous value was less than zero, - /// current task is resumed from suspension. - public func signal() { - _incrementCount() - guard !continuations.isEmpty else { return } - let (_, continuation) = continuations.removeFirst() - continuation.resume() + /// If any previous task is waiting for access to semaphore, + /// then the task is resumed from suspension. + public nonisolated func signal() { + Task { await _signal() } } /// Waits for, or decrements, a semaphore. /// /// Decrement the counting semaphore. If the resulting value is less than zero, /// current task is suspended until a signal occurs. + /// + /// - Throws: `CancellationError` if cancelled. @Sendable - public func wait() async { + public func wait() async throws { guard count <= 1 else { count -= 1; return } - try? await _withPromisedContinuation() + try await _withPromisedContinuation() } } diff --git a/Sources/AsyncObjects/CancellationSource.swift b/Sources/AsyncObjects/CancellationSource.swift index f725e1ee..fbfaa78a 100644 --- a/Sources/AsyncObjects/CancellationSource.swift +++ b/Sources/AsyncObjects/CancellationSource.swift @@ -62,12 +62,21 @@ public actor CancellationSource { } } + /// Trigger cancellation event, initiate cooperative cancellation of registered tasks + /// and propagate cancellation to linked cancellation sources. + @Sendable + public func _cancel() async { + registeredTasks.forEach { $1() } + registeredTasks = [:] + await _propagateCancellation() + } + // MARK: Public /// Creates a new cancellation source object. /// /// - Returns: The newly created cancellation source. - public init() { } + public init() {} #if swift(>=5.7) /// Creates a new cancellation source object linking to all the provided cancellation sources. @@ -78,12 +87,15 @@ public actor CancellationSource { /// - Parameter sources: The cancellation sources the newly created object will be linked to. /// /// - Returns: The newly created cancellation source. - public nonisolated init(linkedWith sources: [CancellationSource]) async { - await withTaskGroup(of: Void.self) { group in - sources.forEach { source in - group.addTask { await source._addSource(self) } + public init(linkedWith sources: [CancellationSource]) { + self.init() + Task { + await withTaskGroup(of: Void.self) { group in + sources.forEach { source in + group.addTask { await source._addSource(self) } + } + await group.waitForAll() } - await group.waitForAll() } } @@ -95,8 +107,8 @@ public actor CancellationSource { /// - Parameter sources: The cancellation sources the newly created object will be linked to. /// /// - Returns: The newly created cancellation source. - public init(linkedWith sources: CancellationSource...) async { - await self.init(linkedWith: sources) + public init(linkedWith sources: CancellationSource...) { + self.init(linkedWith: sources) } /// Creates a new cancellation source object @@ -120,12 +132,15 @@ public actor CancellationSource { /// - Parameter sources: The cancellation sources the newly created object will be linked to. /// /// - Returns: The newly created cancellation source. - public init(linkedWith sources: [CancellationSource]) async { - await withTaskGroup(of: Void.self) { group in - sources.forEach { source in - group.addTask { await source._addSource(self) } + public convenience init(linkedWith sources: [CancellationSource]) { + self.init() + Task { + await withTaskGroup(of: Void.self) { group in + sources.forEach { source in + group.addTask { await source._addSource(self) } + } + await group.waitForAll() } - await group.waitForAll() } } @@ -137,8 +152,8 @@ public actor CancellationSource { /// - Parameter sources: The cancellation sources the newly created object will be linked to. /// /// - Returns: The newly created cancellation source. - public convenience init(linkedWith sources: CancellationSource...) async { - await self.init(linkedWith: sources) + public convenience init(linkedWith sources: CancellationSource...) { + self.init(linkedWith: sources) } /// Creates a new cancellation source object @@ -160,9 +175,11 @@ public actor CancellationSource { /// If task completes before cancellation event is triggered, it is automatically unregistered. /// /// - Parameter task: The task to register. - public func register(task: Task) { - _add(task: task) + public nonisolated func register( + task: Task + ) { Task { [weak self] in + await self?._add(task: task) let _ = await task.result await self?._remove(task: task) } @@ -171,10 +188,8 @@ public actor CancellationSource { /// Trigger cancellation event, initiate cooperative cancellation of registered tasks /// and propagate cancellation to linked cancellation sources. @Sendable - public func cancel() async { - registeredTasks.forEach { $1() } - registeredTasks = [:] - await _propagateCancellation() + public nonisolated func cancel() { + Task { await _cancel() } } /// Trigger cancellation event after provided delay, @@ -186,119 +201,11 @@ public actor CancellationSource { @Sendable public func cancel(afterNanoseconds nanoseconds: UInt64) async throws { try await Task.sleep(nanoseconds: nanoseconds) - await cancel() + await _cancel() } } public extension Task { - /// Runs the given non-throwing operation asynchronously as part of a new task on behalf of the current actor, - /// with the provided cancellation source controlling cooperative cancellation. - /// - /// A top-level task with the provided operation is created, cancellation of which is controlled by provided cancellation source. - /// In the event of cancellation top-level task is cancelled, while returning the value in the returned task. - /// - /// - Parameters: - /// - priority: The priority of the task. Pass `nil` to use the priority from `Task.currentPriority`. - /// - cancellationSource: The cancellation source on which new task will be registered for cancellation. - /// - operation: The operation to perform. - /// - /// - Returns: The newly created task. - /// - Note: In case you want to register and track the top-level task - /// for cancellation use the async initializer instead. - @discardableResult - init( - priority: TaskPriority? = nil, - cancellationSource: CancellationSource, - operation: @escaping @Sendable () async -> Success - ) where Failure == Never { - self.init(priority: priority) { - let task = Self.init(priority: priority, operation: operation) - await cancellationSource.register(task: task) - return await task.value - } - } - - /// Runs the given throwing operation asynchronously as part of a new task on behalf of the current actor, - /// with the provided cancellation source controlling cooperative cancellation. - /// - /// A top-level task with the provided operation is created, cancellation of which is controlled by provided cancellation source. - /// In the event of cancellation top-level task is cancelled, while propagating error in the returned task. - /// - /// - Parameters: - /// - priority: The priority of the task. Pass `nil` to use the priority from `Task.currentPriority`. - /// - cancellationSource: The cancellation source on which new task will be registered for cancellation. - /// - operation: The operation to perform. - /// - /// - Returns: The newly created task. - /// - Note: In case you want to register and track the top-level task - /// for cancellation use the async initializer instead. - @discardableResult - init( - priority: TaskPriority? = nil, - cancellationSource: CancellationSource, - operation: @escaping @Sendable () async throws -> Success - ) where Failure == Error { - self.init(priority: priority) { - let task = Self.init(priority: priority, operation: operation) - await cancellationSource.register(task: task) - return try await task.value - } - } - - /// Runs the given non-throwing operation asynchronously as part of a new task, - /// with the provided cancellation source controlling cooperative cancellation. - /// - /// A top-level task with the provided operation is created, cancellation of which is controlled by provided cancellation source. - /// In the event of cancellation top-level task is cancelled, while returning the value in the returned task. - /// - /// - Parameters: - /// - priority: The priority of the task. - /// - cancellationSource: The cancellation source on which new task will be registered for cancellation. - /// - operation: The operation to perform. - /// - /// - Returns: The newly created task. - /// - Note: In case you want to register and track the top-level task - /// for cancellation use the async initializer instead. - @discardableResult - static func detached( - priority: TaskPriority? = nil, - cancellationSource: CancellationSource, - operation: @escaping @Sendable () async -> Success - ) -> Self where Failure == Never { - return Task.detached(priority: priority) { - let task = Self.init(priority: priority, operation: operation) - await cancellationSource.register(task: task) - return await task.value - } - } - - /// Runs the given throwing operation asynchronously as part of a new task, - /// with the provided cancellation source controlling cooperative cancellation. - /// - /// A top-level task with the provided operation is created, cancellation of which is controlled by provided cancellation source. - /// In the event of cancellation top-level task is cancelled, while returning the value in the returned task. - /// - /// - Parameters: - /// - priority: The priority of the task. - /// - cancellationSource: The cancellation source on which new task will be registered for cancellation. - /// - operation: The operation to perform. - /// - /// - Returns: The newly created task. - /// - Note: In case you want to register and track the top-level task - /// for cancellation use the async initializer instead. - @discardableResult - static func detached( - priority: TaskPriority? = nil, - cancellationSource: CancellationSource, - operation: @escaping @Sendable () async throws -> Success - ) -> Self where Failure == Error { - return Task.detached(priority: priority) { - let task = Self.init(priority: priority, operation: operation) - await cancellationSource.register(task: task) - return try await task.value - } - } - /// Runs the given non-throwing operation asynchronously as part of a new top-level task on behalf of the current actor, /// with the provided cancellation source controlling cooperative cancellation. /// @@ -315,9 +222,9 @@ public extension Task { priority: TaskPriority? = nil, cancellationSource: CancellationSource, operation: @escaping @Sendable () async -> Success - ) async where Failure == Never { + ) where Failure == Never { self.init(priority: priority, operation: operation) - await cancellationSource.register(task: self) + cancellationSource.register(task: self) } /// Runs the given throwing operation asynchronously as part of a new top-level task on behalf of the current actor, @@ -336,9 +243,9 @@ public extension Task { priority: TaskPriority? = nil, cancellationSource: CancellationSource, operation: @escaping @Sendable () async throws -> Success - ) async where Failure == Error { + ) where Failure == Error { self.init(priority: priority, operation: operation) - await cancellationSource.register(task: self) + cancellationSource.register(task: self) } /// Runs the given non-throwing operation asynchronously as part of a new top-level task, @@ -357,9 +264,9 @@ public extension Task { priority: TaskPriority? = nil, cancellationSource: CancellationSource, operation: @escaping @Sendable () async -> Success - ) async -> Self where Failure == Never { + ) -> Self where Failure == Never { let task = Task.detached(priority: priority, operation: operation) - await cancellationSource.register(task: task) + cancellationSource.register(task: task) return task } @@ -379,9 +286,9 @@ public extension Task { priority: TaskPriority? = nil, cancellationSource: CancellationSource, operation: @escaping @Sendable () async throws -> Success - ) async -> Self where Failure == Error { + ) -> Self where Failure == Error { let task = Task.detached(priority: priority, operation: operation) - await cancellationSource.register(task: task) + cancellationSource.register(task: task) return task } } diff --git a/Sources/AsyncObjects/TaskOperation.swift b/Sources/AsyncObjects/TaskOperation.swift index 5d7769e2..2034c4e4 100644 --- a/Sources/AsyncObjects/TaskOperation.swift +++ b/Sources/AsyncObjects/TaskOperation.swift @@ -14,7 +14,21 @@ import Dispatch /// You can start the operation by adding it to an `OperationQueue`, /// or by manually calling the ``signal()`` or ``start()`` method. /// Wait for operation completion asynchronously by calling ``wait()`` method -/// or its timeout variation ``wait(forNanoseconds:)``. +/// or its timeout variation ``wait(forNanoseconds:)``: +/// +/// ```swift +/// // create operation with async action +/// let operation = TaskOperation { try await Task.sleep(nanoseconds: 1_000_000_000) } +/// // start operation to execute action +/// operation.start() // operation.signal() +/// +/// // wait for operation completion asynchrnously, fails only if task cancelled +/// try await operation.wait() +/// // or wait with some timeout +/// try await operation.wait(forNanoseconds: 1_000_000_000) +/// // or wait synchronously for completion +/// operation.waitUntilFinished() +/// ``` public final class TaskOperation: Operation, AsyncObject, @unchecked Sendable { @@ -252,10 +266,12 @@ public final class TaskOperation: Operation, AsyncObject, /// /// Only waits asynchronously, if operation is executing, /// until it is completed or cancelled. + /// + /// - Throws: `CancellationError` if cancelled. @Sendable - public func wait() async { + public func wait() async throws { guard !isFinished else { return } - try? await _withPromisedContinuation() + try await _withPromisedContinuation() } } diff --git a/Sources/AsyncObjects/TaskQueue.swift b/Sources/AsyncObjects/TaskQueue.swift index afde3f11..713ffe4d 100644 --- a/Sources/AsyncObjects/TaskQueue.swift +++ b/Sources/AsyncObjects/TaskQueue.swift @@ -513,16 +513,16 @@ public actor TaskQueue: AsyncObject { /// Signalling on queue does nothing. /// Only added to satisfy ``AsyncObject`` requirements. - public func signal() { - // Do nothing - } + public nonisolated func signal() { /* Do nothing */ } /// Waits for execution turn on queue. /// /// Only waits asynchronously, if queue is locked by a barrier task, /// until the suspended task's turn comes to be resumed. + /// + /// - Throws: `CancellationError` if cancelled. @Sendable - public func wait() async { - await exec { /*Do nothing*/ } + public func wait() async throws { + try await exec { try await Task.sleep(nanoseconds: 0) } } } diff --git a/Tests/AsyncObjectsTests/AsyncCountdownEventTests.swift b/Tests/AsyncObjectsTests/AsyncCountdownEventTests.swift index fc0bc632..19dc95a5 100644 --- a/Tests/AsyncObjectsTests/AsyncCountdownEventTests.swift +++ b/Tests/AsyncObjectsTests/AsyncCountdownEventTests.swift @@ -6,16 +6,15 @@ class AsyncCountdownEventTests: XCTestCase { func testCountdownWaitWithoutIncrement() async throws { let event = AsyncCountdownEvent() - await Self.checkExecInterval(durationInSeconds: 0) { - await event.wait() + try await Self.checkExecInterval(durationInSeconds: 0) { + try await event.wait() } } func testCountdownWaitZeroTimeoutWithoutIncrement() async throws { let event = AsyncCountdownEvent() - await Self.checkExecInterval(durationInSeconds: 0) { - let result = await event.wait(forSeconds: 0) - XCTAssertEqual(result, .success) + try await Self.checkExecInterval(durationInSeconds: 0) { + try await event.wait(forSeconds: 0) } } @@ -29,7 +28,7 @@ class AsyncCountdownEventTests: XCTestCase { group.addTask { let duration = UInt64(Double(i + 1) * 5E8) try await Task.sleep(nanoseconds: duration) - await event.signal() + event.signal(repeat: 1) } } try await group.waitForAll() @@ -39,48 +38,61 @@ class AsyncCountdownEventTests: XCTestCase { func testCountdownWaitWithIncrement() async throws { let event = AsyncCountdownEvent() - await event.increment(by: 10) + event.increment(by: 10) + try await Self.sleep(forSeconds: 0.001) Self.signalCountdownEvent(event, times: 10) - await Self.checkExecInterval(durationInSeconds: 5) { - await event.wait() + try await Self.checkExecInterval(durationInSeconds: 5) { + try await event.wait() } } func testCountdownWaitTimeoutWithIncrement() async throws { let event = AsyncCountdownEvent() - await event.increment(by: 10) + event.increment(by: 10) + try await Self.sleep(forSeconds: 0.001) Self.signalCountdownEvent(event, times: 10) await Self.checkExecInterval(durationInSeconds: 3) { - let result = await event.wait(forSeconds: 3) - XCTAssertEqual(result, .timedOut) + do { + try await event.wait(forSeconds: 3) + XCTFail("Unexpected task progression") + } catch { + XCTAssertTrue(type(of: error) == DurationTimeoutError.self) + } } } func testCountdownWaitWithLimitAndIncrement() async throws { let event = AsyncCountdownEvent(until: 3) - await event.increment(by: 10) + event.increment(by: 10) + try await Self.sleep(forSeconds: 0.001) Self.signalCountdownEvent(event, times: 10) - await Self.checkExecInterval(durationInRange: 3.5..<4) { - await event.wait() + try await Self.checkExecInterval(durationInRange: 3.5..<4) { + try await event.wait() } } func testCountdownWaitTimeoutWithLimitAndIncrement() async throws { let event = AsyncCountdownEvent(until: 3) - await event.increment(by: 10) + event.increment(by: 10) + try await Self.sleep(forSeconds: 0.001) Self.signalCountdownEvent(event, times: 10) await Self.checkExecInterval(durationInSeconds: 2) { - let result = await event.wait(forSeconds: 2) - XCTAssertEqual(result, .timedOut) + do { + try await event.wait(forSeconds: 2) + XCTFail("Unexpected task progression") + } catch { + XCTAssertTrue(type(of: error) == DurationTimeoutError.self) + } } } func testCountdownWaitWithLimitInitialCountAndIncrement() async throws { let event = AsyncCountdownEvent(until: 3, initial: 2) - await event.increment(by: 10) + event.increment(by: 10) + try await Self.sleep(forSeconds: 0.001) Self.signalCountdownEvent(event, times: 10) - await Self.checkExecInterval(durationInRange: 4.5..<5) { - await event.wait() + try await Self.checkExecInterval(durationInRange: 4.5..<5) { + try await event.wait() } } @@ -88,75 +100,95 @@ class AsyncCountdownEventTests: XCTestCase { async throws { let event = AsyncCountdownEvent(until: 3, initial: 3) - await event.increment(by: 10) + event.increment(by: 10) + try await Self.sleep(forSeconds: 0.001) Self.signalCountdownEvent(event, times: 10) await Self.checkExecInterval(durationInSeconds: 3) { - let result = await event.wait(forSeconds: 3) - XCTAssertEqual(result, .timedOut) + do { + try await event.wait(forSeconds: 3) + XCTFail("Unexpected task progression") + } catch { + XCTAssertTrue(type(of: error) == DurationTimeoutError.self) + } } } func testCountdownWaitWithIncrementAndReset() async throws { let event = AsyncCountdownEvent() - await event.increment(by: 10) + event.increment(by: 10) + try await Self.sleep(forSeconds: 0.001) Task.detached { try await Self.sleep(seconds: 3) - await event.reset() + event.reset() } - await Self.checkExecInterval(durationInSeconds: 3) { - await event.wait() + try await Self.checkExecInterval(durationInSeconds: 3) { + try await event.wait() } } func testCountdownWaitWithIncrementAndResetToCount() async throws { let event = AsyncCountdownEvent() - await event.increment(by: 10) + event.increment(by: 10) + try await Self.sleep(forSeconds: 0.001) Task.detached { try await Self.sleep(seconds: 3) - await event.reset(to: 2) + event.reset(to: 2) await Self.signalCountdownEvent(event, times: 10) } - await Self.checkExecInterval(durationInSeconds: 4) { - await event.wait() + try await Self.checkExecInterval(durationInSeconds: 4) { + try await event.wait() } } func testCountdownWaitTimeoutWithIncrementAndReset() async throws { let event = AsyncCountdownEvent() - await event.increment(by: 10) + event.increment(by: 10) + try await Self.sleep(forSeconds: 0.001) Task.detached { try await Self.sleep(seconds: 3) - await event.reset() + event.reset() } await Self.checkExecInterval(durationInSeconds: 2) { - await event.wait(forSeconds: 2) + do { + try await event.wait(forSeconds: 2) + XCTFail("Unexpected task progression") + } catch { + XCTAssertTrue(type(of: error) == DurationTimeoutError.self) + } } } func testCountdownWaitTimeoutWithIncrementAndResetToCount() async throws { let event = AsyncCountdownEvent() - await event.increment(by: 10) + event.increment(by: 10) + try await Self.sleep(forSeconds: 0.001) Task.detached { try await Self.sleep(seconds: 3) - await event.reset(to: 6) + event.reset(to: 6) await Self.signalCountdownEvent(event, times: 10) } await Self.checkExecInterval(durationInSeconds: 3) { - await event.wait(forSeconds: 3) + do { + try await event.wait(forSeconds: 3) + XCTFail("Unexpected task progression") + } catch { + XCTAssertTrue(type(of: error) == DurationTimeoutError.self) + } } } func testCountdownWaitWithConcurrentIncrementAndResetToCount() async throws { let event = AsyncCountdownEvent() - await event.increment(by: 10) + event.increment(by: 10) + try await Self.sleep(forSeconds: 0.001) Task.detached { try await Self.sleep(seconds: 2) - await event.reset(to: 2) + event.reset(to: 2) } Self.signalCountdownEvent(event, times: 10) - await Self.checkExecInterval(durationInRange: 2.5...3.2) { - await event.wait() + try await Self.checkExecInterval(durationInRange: 2.5...3.2) { + try await event.wait() } } @@ -164,9 +196,9 @@ class AsyncCountdownEventTests: XCTestCase { let event = AsyncCountdownEvent(until: 0, initial: 1) Task.detached { try await Self.sleep(seconds: 1) - await event.signal() + event.signal() } - await event.wait() + try await event.wait() self.addTeardownBlock { [weak event] in XCTAssertNil(event) } @@ -175,44 +207,44 @@ class AsyncCountdownEventTests: XCTestCase { func testWaitCancellationWhenTaskCancelled() async throws { let event = AsyncCountdownEvent(initial: 1) let task = Task.detached { - await Self.checkExecInterval(durationInSeconds: 0) { - await event.wait() + try await Self.checkExecInterval(durationInSeconds: 0) { + try await event.wait() } } task.cancel() - await task.value + try? await task.value } func testWaitCancellationForAlreadyCancelledTask() async throws { let event = AsyncCountdownEvent(initial: 1) let task = Task.detached { - await Self.checkExecInterval(durationInSeconds: 0) { + try await Self.checkExecInterval(durationInSeconds: 0) { do { try await Self.sleep(seconds: 5) XCTFail("Unexpected task progression") } catch {} XCTAssertTrue(Task.isCancelled) - await event.wait() + try await event.wait() } } task.cancel() - await task.value + try? await task.value } - func testConcurrentAccess() async { - await withTaskGroup(of: Void.self) { group in + func testConcurrentAccess() async throws { + try await withThrowingTaskGroup(of: Void.self) { group in for _ in 0..<10 { group.addTask { let event = AsyncCountdownEvent(initial: 1) - await Self.checkExecInterval(durationInSeconds: 0) { - await withTaskGroup(of: Void.self) { group in - group.addTask { await event.wait() } - group.addTask { await event.signal() } - await group.waitForAll() + try await Self.checkExecInterval(durationInSeconds: 0) { + try await withThrowingTaskGroup(of: Void.self) { g in + g.addTask { try await event.wait() } + g.addTask { event.signal() } + try await g.waitForAll() } } } - await group.waitForAll() + try await group.waitForAll() } } } diff --git a/Tests/AsyncObjectsTests/AsyncEventTests.swift b/Tests/AsyncObjectsTests/AsyncEventTests.swift index 4c7ff616..550a2587 100644 --- a/Tests/AsyncObjectsTests/AsyncEventTests.swift +++ b/Tests/AsyncObjectsTests/AsyncEventTests.swift @@ -11,9 +11,9 @@ class AsyncEventTests: XCTestCase { ) async throws { Task.detached { try await Self.sleep(seconds: interval) - await event.signal() + event.signal() } - await Self.checkExecInterval( + try await Self.checkExecInterval( durationInSeconds: seconds, for: event.wait ) @@ -26,7 +26,8 @@ class AsyncEventTests: XCTestCase { func testEventLockAndWait() async throws { let event = AsyncEvent() - await event.reset() + event.reset() + try await Self.sleep(forSeconds: 0.001) try await checkWait(for: event) } @@ -37,51 +38,48 @@ class AsyncEventTests: XCTestCase { func testEventWaitWithTimeout() async throws { let event = AsyncEvent(signaledInitially: false) - var result: TaskTimeoutResult = .success await Self.checkExecInterval(durationInSeconds: 1) { - result = await event.wait(forSeconds: 1) + do { + try await event.wait(forSeconds: 1) + XCTFail("Unexpected task progression") + } catch { + XCTAssertTrue(type(of: error) == DurationTimeoutError.self) + } } - XCTAssertEqual(result, .timedOut) } func testEventWaitWithZeroTimeout() async throws { let event = AsyncEvent(signaledInitially: true) - var result: TaskTimeoutResult = .success - await Self.checkExecInterval(durationInSeconds: 0) { - result = await event.wait(forNanoseconds: 0) + try await Self.checkExecInterval(durationInSeconds: 0) { + try await event.wait(forNanoseconds: 0) } - XCTAssertEqual(result, .success) } func testEventWaitSuccessWithoutTimeout() async throws { let event = AsyncEvent(signaledInitially: false) - var result: TaskTimeoutResult = .timedOut Task.detached { try await Self.sleep(seconds: 1) - await event.signal() + event.signal() } - await Self.checkExecInterval(durationInSeconds: 1) { - result = await event.wait(forSeconds: 2) + try await Self.checkExecInterval(durationInSeconds: 1) { + try await event.wait(forSeconds: 2) } - XCTAssertEqual(result, .success) } func testReleasedEventWaitSuccessWithoutTimeout() async throws { let event = AsyncEvent() - var result: TaskTimeoutResult = .timedOut - await Self.checkExecInterval(durationInSeconds: 0) { - result = await event.wait(forSeconds: 2) + try await Self.checkExecInterval(durationInSeconds: 0) { + try await event.wait(forSeconds: 2) } - XCTAssertEqual(result, .success) } func testDeinit() async throws { let event = AsyncEvent(signaledInitially: false) Task.detached { try await Self.sleep(seconds: 1) - await event.signal() + event.signal() } - await event.wait() + try await event.wait() self.addTeardownBlock { [weak event] in XCTAssertNil(event) } @@ -91,7 +89,12 @@ class AsyncEventTests: XCTestCase { let event = AsyncEvent(signaledInitially: false) let task = Task.detached { await Self.checkExecInterval(durationInSeconds: 0) { - await event.wait() + do { + try await event.wait() + XCTFail("Unexpected task progression") + } catch { + XCTAssertTrue(type(of: error) == CancellationError.self) + } } } task.cancel() @@ -107,27 +110,27 @@ class AsyncEventTests: XCTestCase { XCTFail("Unexpected task progression") } catch {} XCTAssertTrue(Task.isCancelled) - await event.wait() + try? await event.wait() } } task.cancel() await task.value } - func testConcurrentAccess() async { - await withTaskGroup(of: Void.self) { group in + func testConcurrentAccess() async throws { + try await withThrowingTaskGroup(of: Void.self) { group in for _ in 0..<10 { group.addTask { let event = AsyncEvent(signaledInitially: false) - await Self.checkExecInterval(durationInSeconds: 0) { - await withTaskGroup(of: Void.self) { group in - group.addTask { await event.wait() } - group.addTask { await event.signal() } - await group.waitForAll() + try await Self.checkExecInterval(durationInSeconds: 0) { + try await withThrowingTaskGroup(of: Void.self) { g in + g.addTask { try await event.wait() } + g.addTask { event.signal() } + try await g.waitForAll() } } } - await group.waitForAll() + try await group.waitForAll() } } } diff --git a/Tests/AsyncObjectsTests/AsyncObjectTests.swift b/Tests/AsyncObjectsTests/AsyncObjectTests.swift index 182747f4..e68a7fc1 100644 --- a/Tests/AsyncObjectsTests/AsyncObjectTests.swift +++ b/Tests/AsyncObjectsTests/AsyncObjectTests.swift @@ -9,11 +9,11 @@ class AsyncObjectTests: XCTestCase { let mutex = AsyncSemaphore() Task.detached { try await Self.sleep(seconds: 1) - await event.signal() - await mutex.signal() + event.signal() + mutex.signal() } - await Self.checkExecInterval(durationInSeconds: 1) { - await waitForAll(event, mutex) + try await Self.checkExecInterval(durationInSeconds: 1) { + try await waitForAll(event, mutex) } } @@ -22,12 +22,12 @@ class AsyncObjectTests: XCTestCase { let mutex = AsyncSemaphore() Task.detached { try await Self.sleep(seconds: 1) - await event.signal() + event.signal() try await Self.sleep(seconds: 1) - await mutex.signal() + mutex.signal() } - await Self.checkExecInterval(durationInSeconds: 1) { - await waitForAny(event, mutex) + try await Self.checkExecInterval(durationInSeconds: 1) { + try await waitForAny(event, mutex) } } @@ -39,46 +39,52 @@ class AsyncObjectTests: XCTestCase { } Task.detached { try await Self.sleep(seconds: 1) - await event.signal() + event.signal() } Task.detached { try await Self.sleep(seconds: 2) - await mutex.signal() + mutex.signal() } op.signal() - await Self.checkExecInterval(durationInSeconds: 2) { - await waitForAny(event, mutex, op, count: 2) + try await Self.checkExecInterval(durationInSeconds: 2) { + try await waitForAny(event, mutex, op, count: 2) } } func testMultipleObjectWaitAllWithTimeout() async throws { let event = AsyncEvent(signaledInitially: false) let mutex = AsyncSemaphore() - var result: TaskTimeoutResult = .success await Self.checkExecInterval(durationInSeconds: 1) { - result = await waitForAll( - event, mutex, - forNanoseconds: UInt64(1E9) - ) + do { + try await waitForAll( + event, mutex, + forNanoseconds: UInt64(1E9) + ) + XCTFail("Unexpected task progression") + } catch { + XCTAssertTrue(type(of: error) == DurationTimeoutError.self) + } } - XCTAssertEqual(result, .timedOut) } func testMultipleObjectWaitAnyWithTimeout() async throws { let event = AsyncEvent(signaledInitially: false) let mutex = AsyncSemaphore() - var result: TaskTimeoutResult = .success await Self.checkExecInterval(durationInSeconds: 1) { - result = await waitForAny( - event, mutex, - forNanoseconds: UInt64(1E9) - ) + do { + try await waitForAny( + event, mutex, + count: 2, + forNanoseconds: UInt64(1E9) + ) + XCTFail("Unexpected task progression") + } catch { + XCTAssertTrue(type(of: error) == DurationTimeoutError.self) + } } - XCTAssertEqual(result, .timedOut) } func testMultipleObjectWaitMultipleWithTimeout() async throws { - var result: TaskTimeoutResult = .success let event = AsyncEvent(signaledInitially: false) let mutex = AsyncSemaphore() let op = TaskOperation { @@ -86,57 +92,57 @@ class AsyncObjectTests: XCTestCase { } Task.detached { try await Self.sleep(seconds: 1) - await event.signal() + event.signal() } Task.detached { try await Self.sleep(seconds: 3) - await mutex.signal() + mutex.signal() } op.signal() await Self.checkExecInterval(durationInSeconds: 2) { - result = await waitForAny( - event, mutex, op, - count: 2, - forNanoseconds: UInt64(2E9) - ) + do { + try await waitForAny( + event, mutex, op, + count: 2, + forNanoseconds: UInt64(2E9) + ) + XCTFail("Unexpected task progression") + } catch { + XCTAssertTrue(type(of: error) == DurationTimeoutError.self) + } } - XCTAssertEqual(result, .timedOut) } func testMultipleObjectWaitAllWithoutTimeout() async throws { let event = AsyncEvent(signaledInitially: false) let mutex = AsyncSemaphore() - var result: TaskTimeoutResult = .timedOut Task.detached { try await Self.sleep(seconds: 1) - await event.signal() - await mutex.signal() + event.signal() + mutex.signal() } - await Self.checkExecInterval(durationInSeconds: 1) { - result = await waitForAll( + try await Self.checkExecInterval(durationInSeconds: 1) { + try await waitForAll( event, mutex, forNanoseconds: UInt64(2E9) ) } - XCTAssertEqual(result, .success) } func testMultipleObjectWaitAnyWithoutTimeout() async throws { let event = AsyncEvent(signaledInitially: false) let mutex = AsyncSemaphore() - var result: TaskTimeoutResult = .timedOut Task.detached { try await Self.sleep(seconds: 1) - await event.signal() + event.signal() try await Self.sleep(seconds: 1) - await mutex.signal() + mutex.signal() } - await Self.checkExecInterval(durationInSeconds: 1) { - result = await waitForAny( + try await Self.checkExecInterval(durationInSeconds: 1) { + try await waitForAny( event, mutex, forNanoseconds: UInt64(2E9) ) } - XCTAssertEqual(result, .success) } } diff --git a/Tests/AsyncObjectsTests/AsyncSemaphoreTests.swift b/Tests/AsyncObjectsTests/AsyncSemaphoreTests.swift index b6784b86..39e99460 100644 --- a/Tests/AsyncObjectsTests/AsyncSemaphoreTests.swift +++ b/Tests/AsyncObjectsTests/AsyncSemaphoreTests.swift @@ -14,9 +14,9 @@ class AsyncSemaphoreTests: XCTestCase { try await withThrowingTaskGroup(of: Void.self) { group in for _ in 0.. TaskTimeoutResult { - return await self.wait(forNanoseconds: seconds * 1_000_000_000) + func wait(forSeconds seconds: UInt64) async throws { + return try await self.wait(forNanoseconds: seconds * 1_000_000_000) } } From 75fb521d878018f182b94f53fc7fc8035dc674df Mon Sep 17 00:00:00 2001 From: Soumya Ranjan Mahunt Date: Sun, 4 Sep 2022 23:02:01 +0530 Subject: [PATCH 02/14] wip: add delay to deinit tests --- Tests/AsyncObjectsTests/AsyncCountdownEventTests.swift | 1 + Tests/AsyncObjectsTests/AsyncEventTests.swift | 1 + Tests/AsyncObjectsTests/AsyncSemaphoreTests.swift | 1 + Tests/AsyncObjectsTests/CancellationSourceTests.swift | 1 + Tests/AsyncObjectsTests/NonThrowingFutureTests.swift | 1 + Tests/AsyncObjectsTests/TaskOperationTests.swift | 2 ++ Tests/AsyncObjectsTests/TaskQueueTests.swift | 1 + Tests/AsyncObjectsTests/ThrowingFutureTests.swift | 1 + 8 files changed, 9 insertions(+) diff --git a/Tests/AsyncObjectsTests/AsyncCountdownEventTests.swift b/Tests/AsyncObjectsTests/AsyncCountdownEventTests.swift index 19dc95a5..b3bb2958 100644 --- a/Tests/AsyncObjectsTests/AsyncCountdownEventTests.swift +++ b/Tests/AsyncObjectsTests/AsyncCountdownEventTests.swift @@ -200,6 +200,7 @@ class AsyncCountdownEventTests: XCTestCase { } try await event.wait() self.addTeardownBlock { [weak event] in + try await Self.sleep(seconds: 1) XCTAssertNil(event) } } diff --git a/Tests/AsyncObjectsTests/AsyncEventTests.swift b/Tests/AsyncObjectsTests/AsyncEventTests.swift index 550a2587..81ab311b 100644 --- a/Tests/AsyncObjectsTests/AsyncEventTests.swift +++ b/Tests/AsyncObjectsTests/AsyncEventTests.swift @@ -81,6 +81,7 @@ class AsyncEventTests: XCTestCase { } try await event.wait() self.addTeardownBlock { [weak event] in + try await Self.sleep(seconds: 1) XCTAssertNil(event) } } diff --git a/Tests/AsyncObjectsTests/AsyncSemaphoreTests.swift b/Tests/AsyncObjectsTests/AsyncSemaphoreTests.swift index 39e99460..fc12c816 100644 --- a/Tests/AsyncObjectsTests/AsyncSemaphoreTests.swift +++ b/Tests/AsyncObjectsTests/AsyncSemaphoreTests.swift @@ -183,6 +183,7 @@ class AsyncSemaphoreTests: XCTestCase { } try await semaphore.wait() self.addTeardownBlock { [weak semaphore] in + try await Self.sleep(seconds: 1) XCTAssertNil(semaphore) } } diff --git a/Tests/AsyncObjectsTests/CancellationSourceTests.swift b/Tests/AsyncObjectsTests/CancellationSourceTests.swift index b9a0d502..97cf1227 100644 --- a/Tests/AsyncObjectsTests/CancellationSourceTests.swift +++ b/Tests/AsyncObjectsTests/CancellationSourceTests.swift @@ -210,6 +210,7 @@ class CancellationSourceTests: XCTestCase { } try? await task.value self.addTeardownBlock { [weak source] in + try await Self.sleep(seconds: 1) XCTAssertNil(source) } } diff --git a/Tests/AsyncObjectsTests/NonThrowingFutureTests.swift b/Tests/AsyncObjectsTests/NonThrowingFutureTests.swift index 5db615aa..6df1b53f 100644 --- a/Tests/AsyncObjectsTests/NonThrowingFutureTests.swift +++ b/Tests/AsyncObjectsTests/NonThrowingFutureTests.swift @@ -204,6 +204,7 @@ class NonThrowingFutureTests: XCTestCase { } let _ = await future.value self.addTeardownBlock { [weak future] in + try await Self.sleep(seconds: 1) XCTAssertNil(future) } } diff --git a/Tests/AsyncObjectsTests/TaskOperationTests.swift b/Tests/AsyncObjectsTests/TaskOperationTests.swift index b6166e0e..6d981c92 100644 --- a/Tests/AsyncObjectsTests/TaskOperationTests.swift +++ b/Tests/AsyncObjectsTests/TaskOperationTests.swift @@ -173,6 +173,7 @@ class TaskOperationTests: XCTestCase { } operation.signal() self.addTeardownBlock { [weak operation] in + try await Self.sleep(seconds: 1) XCTAssertNil(operation) } } @@ -182,6 +183,7 @@ class TaskOperationTests: XCTestCase { operation.signal() try await operation.wait() self.addTeardownBlock { [weak operation] in + try await Self.sleep(seconds: 1) XCTAssertNil(operation) } } diff --git a/Tests/AsyncObjectsTests/TaskQueueTests.swift b/Tests/AsyncObjectsTests/TaskQueueTests.swift index 5f206861..e542b5e8 100644 --- a/Tests/AsyncObjectsTests/TaskQueueTests.swift +++ b/Tests/AsyncObjectsTests/TaskQueueTests.swift @@ -743,6 +743,7 @@ class TaskQueueTests: XCTestCase { } try await Self.sleep(forSeconds: 0.001) self.addTeardownBlock { [weak queue] in + try await Self.sleep(seconds: 1) XCTAssertNil(queue) } } diff --git a/Tests/AsyncObjectsTests/ThrowingFutureTests.swift b/Tests/AsyncObjectsTests/ThrowingFutureTests.swift index 7b42dd6d..ccb1ed0e 100644 --- a/Tests/AsyncObjectsTests/ThrowingFutureTests.swift +++ b/Tests/AsyncObjectsTests/ThrowingFutureTests.swift @@ -424,6 +424,7 @@ class ThrowingFutureTests: XCTestCase { } let _ = try await future.value self.addTeardownBlock { [weak future] in + try await Self.sleep(seconds: 1) XCTAssertNil(future) } } From cd9d5ec1796c3cf71e779df7866ff61212923b46 Mon Sep 17 00:00:00 2001 From: Soumya Ranjan Mahunt Date: Mon, 5 Sep 2022 09:24:19 +0530 Subject: [PATCH 03/14] wip: add delay for tests where needed --- Tests/AsyncObjectsTests/AsyncSemaphoreTests.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/AsyncObjectsTests/AsyncSemaphoreTests.swift b/Tests/AsyncObjectsTests/AsyncSemaphoreTests.swift index fc12c816..147f0ce5 100644 --- a/Tests/AsyncObjectsTests/AsyncSemaphoreTests.swift +++ b/Tests/AsyncObjectsTests/AsyncSemaphoreTests.swift @@ -79,6 +79,7 @@ class AsyncSemaphoreTests: XCTestCase { func testSignaledSemaphoreWaitWithTasksGreaterThanCount() async throws { let semaphore = AsyncSemaphore(value: 3) semaphore.signal() + try await Self.sleep(forSeconds: 0.001) try await checkSemaphoreWait( for: semaphore, taskCount: 4, From ce69a76d8dfdbb1a1377793c916d28e08952f1ee Mon Sep 17 00:00:00 2001 From: Soumya Ranjan Mahunt Date: Mon, 5 Sep 2022 13:12:55 +0530 Subject: [PATCH 04/14] wip: make future initializer synchrnous --- .../AsyncObjects/AsyncCountdownEvent.swift | 35 +++-- Sources/AsyncObjects/AsyncEvent.swift | 25 +-- Sources/AsyncObjects/AsyncObject.swift | 3 +- Sources/AsyncObjects/AsyncSemaphore.swift | 30 ++-- Sources/AsyncObjects/CancellationSource.swift | 38 +++-- Sources/AsyncObjects/Future.swift | 145 ++++++++++++------ Sources/AsyncObjects/Locker.swift | 52 ++++--- Sources/AsyncObjects/SafeContinuation.swift | 2 +- Sources/AsyncObjects/TaskOperation.swift | 27 ++-- Sources/AsyncObjects/TaskQueue.swift | 71 +++++++-- Sources/AsyncObjects/TaskTracker.swift | 6 +- .../CancellationSourceTests.swift | 1 + .../NonThrowingFutureTests.swift | 16 +- .../ThrowingFutureTests.swift | 24 +-- 14 files changed, 316 insertions(+), 159 deletions(-) diff --git a/Sources/AsyncObjects/AsyncCountdownEvent.swift b/Sources/AsyncObjects/AsyncCountdownEvent.swift index 4c30eae5..03c5b7f9 100644 --- a/Sources/AsyncObjects/AsyncCountdownEvent.swift +++ b/Sources/AsyncObjects/AsyncCountdownEvent.swift @@ -23,7 +23,8 @@ import OrderedCollections /// // increment countdown count from high priority tasks /// event.increment(by: 1) /// -/// // wait for countdown signal from low priority tasks, fails only if task cancelled +/// // wait for countdown signal from low priority tasks, +/// // fails only if task cancelled /// try await event.wait() /// // or wait with some timeout /// try await event.wait(forNanoseconds: 1_000_000_000) @@ -37,13 +38,19 @@ import OrderedCollections public actor AsyncCountdownEvent: AsyncObject { /// The suspended tasks continuation type. @usableFromInline - typealias Continuation = SafeContinuation> + internal typealias Continuation = SafeContinuation< + GlobalContinuation + > /// The platform dependent lock used to synchronize continuations tracking. @usableFromInline - let locker: Locker = .init() + internal let locker: Locker = .init() /// The continuations stored with an associated key for all the suspended task that are waiting to be resumed. @usableFromInline - private(set) var continuations: OrderedDictionary = [:] + internal private(set) var continuations: + OrderedDictionary< + UUID, + Continuation + > = [:] /// The limit up to which the countdown counts and triggers event. /// /// By default this is set to zero and can be changed during initialization. @@ -69,13 +76,13 @@ public actor AsyncCountdownEvent: AsyncObject { /// /// - Returns: Whether to wait to be resumed later. @inlinable - func _wait() -> Bool { !isSet || !continuations.isEmpty } + internal func _wait() -> Bool { !isSet || !continuations.isEmpty } /// Resume provided continuation with additional changes based on the associated flags. /// /// - Parameter continuation: The queued continuation to resume. @inlinable - func _resumeContinuation(_ continuation: Continuation) { + internal func _resumeContinuation(_ continuation: Continuation) { currentCount += 1 continuation.resume() } @@ -86,7 +93,7 @@ public actor AsyncCountdownEvent: AsyncObject { /// - continuation: The `continuation` to add. /// - key: The key in the map. @inlinable - func _addContinuation( + internal func _addContinuation( _ continuation: Continuation, withKey key: UUID ) { @@ -100,7 +107,7 @@ public actor AsyncCountdownEvent: AsyncObject { /// /// - Parameter key: The key in the map. @inlinable - func _removeContinuation(withKey key: UUID) { + internal func _removeContinuation(withKey key: UUID) { continuations.removeValue(forKey: key) } @@ -108,7 +115,7 @@ public actor AsyncCountdownEvent: AsyncObject { /// /// - Parameter number: The number to decrement count by. @inlinable - func _decrementCount(by number: UInt = 1) { + internal func _decrementCount(by number: UInt = 1) { defer { _resumeContinuations() } guard currentCount > 0 else { return } currentCount -= number @@ -116,7 +123,7 @@ public actor AsyncCountdownEvent: AsyncObject { /// Resume previously waiting continuations for countdown event. @inlinable - func _resumeContinuations() { + internal func _resumeContinuations() { while !continuations.isEmpty && isSet { let (_, continuation) = continuations.removeFirst() _resumeContinuation(continuation) @@ -132,7 +139,7 @@ public actor AsyncCountdownEvent: AsyncObject { /// /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. @inlinable - nonisolated func _withPromisedContinuation() async throws { + internal nonisolated func _withPromisedContinuation() async throws { let key = UUID() try await Continuation.withCancellation(synchronizedWith: locker) { Task { [weak self] in @@ -149,13 +156,13 @@ public actor AsyncCountdownEvent: AsyncObject { /// /// - Parameter count: The value by which to increase ``currentCount``. @inlinable - func _increment(by count: UInt = 1) { + internal func _increment(by count: UInt = 1) { self.currentCount += count } /// Resets current count to initial count. @inlinable - func _reset() { + internal func _reset() { self.currentCount = initialCount _resumeContinuations() } @@ -164,7 +171,7 @@ public actor AsyncCountdownEvent: AsyncObject { /// /// - Parameter count: The new initial count. @inlinable - func _reset(to count: UInt) { + internal func _reset(to count: UInt) { initialCount = count self.currentCount = count _resumeContinuations() diff --git a/Sources/AsyncObjects/AsyncEvent.swift b/Sources/AsyncObjects/AsyncEvent.swift index 60d917a2..eb55f5cc 100644 --- a/Sources/AsyncObjects/AsyncEvent.swift +++ b/Sources/AsyncObjects/AsyncEvent.swift @@ -14,7 +14,8 @@ import Foundation /// ```swift /// // create event with initial state (signalled or not) /// let event = AsyncEvent(signaledInitially: false) -/// // wait for event to be signalled, fails only if task cancelled +/// // wait for event to be signalled, +/// // fails only if task cancelled /// try await event.wait() /// // or wait with some timeout /// try await event.wait(forNanoseconds: 1_000_000_000) @@ -25,16 +26,18 @@ import Foundation public actor AsyncEvent: AsyncObject { /// The suspended tasks continuation type. @usableFromInline - typealias Continuation = SafeContinuation> + internal typealias Continuation = SafeContinuation< + GlobalContinuation + > /// The platform dependent lock used to synchronize continuations tracking. @usableFromInline - let locker: Locker = .init() + internal let locker: Locker = .init() /// The continuations stored with an associated key for all the suspended task that are waiting for event signal. @usableFromInline - private(set) var continuations: [UUID: Continuation] = [:] + internal private(set) var continuations: [UUID: Continuation] = [:] /// Indicates whether current state of event is signalled. @usableFromInline - var signalled: Bool + internal private(set) var signalled: Bool // MARK: Internal @@ -44,7 +47,7 @@ public actor AsyncEvent: AsyncObject { /// - continuation: The `continuation` to add. /// - key: The key in the map. @inlinable - func _addContinuation( + internal func _addContinuation( _ continuation: Continuation, withKey key: UUID ) { @@ -58,7 +61,7 @@ public actor AsyncEvent: AsyncObject { /// /// - Parameter key: The key in the map. @inlinable - func _removeContinuation(withKey key: UUID) { + internal func _removeContinuation(withKey key: UUID) { continuations.removeValue(forKey: key) } @@ -71,7 +74,7 @@ public actor AsyncEvent: AsyncObject { /// /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. @inlinable - nonisolated func _withPromisedContinuation() async throws { + internal nonisolated func _withPromisedContinuation() async throws { let key = UUID() try await Continuation.withCancellation(synchronizedWith: locker) { Task { [weak self] in @@ -86,14 +89,14 @@ public actor AsyncEvent: AsyncObject { /// Resets signal of event. @inlinable - func _reset() { + internal func _reset() { signalled = false } /// Signals the event and resumes all the tasks /// suspended and waiting for signal. @inlinable - func _signal() { + internal func _signal() { continuations.forEach { $0.value.resume() } continuations = [:] signalled = true @@ -115,6 +118,7 @@ public actor AsyncEvent: AsyncObject { /// Resets signal of event. /// /// After reset, tasks have to wait for event signal to complete. + @Sendable public nonisolated func reset() { Task { await _reset() } } @@ -122,6 +126,7 @@ public actor AsyncEvent: AsyncObject { /// Signals the event. /// /// Resumes all the tasks suspended and waiting for signal. + @Sendable public nonisolated func signal() { Task { await _signal() } } diff --git a/Sources/AsyncObjects/AsyncObject.swift b/Sources/AsyncObjects/AsyncObject.swift index 75a33bdf..d5cb3e30 100644 --- a/Sources/AsyncObjects/AsyncObject.swift +++ b/Sources/AsyncObjects/AsyncObject.swift @@ -16,7 +16,8 @@ public protocol AsyncObject: Sendable { /// Waits asynchronously suspending current task, instead of blocking any thread. /// Async object has to resume the task at a later time depending on its requirement. /// - /// - Throws: `CancellationError` if cancelled. + /// Might throw some error or never throws depending on implementation. + /// /// - Note: Method might return immediately depending upon the synchronization object requirement. @Sendable func wait() async throws diff --git a/Sources/AsyncObjects/AsyncSemaphore.swift b/Sources/AsyncObjects/AsyncSemaphore.swift index bc580c8d..96018a5b 100644 --- a/Sources/AsyncObjects/AsyncSemaphore.swift +++ b/Sources/AsyncObjects/AsyncSemaphore.swift @@ -17,7 +17,8 @@ import OrderedCollections /// ```swift /// // create limiting concurrent access count /// let semaphore = AsyncSemaphore(value: 1) -/// // wait for semaphore access, fails only if task cancelled +/// // wait for semaphore access, +/// // fails only if task cancelled /// try await semaphore.wait() /// // or wait with some timeout /// try await semaphore.wait(forNanoseconds: 1_000_000_000) @@ -27,21 +28,27 @@ import OrderedCollections public actor AsyncSemaphore: AsyncObject { /// The suspended tasks continuation type. @usableFromInline - typealias Continuation = SafeContinuation> + internal typealias Continuation = SafeContinuation< + GlobalContinuation + > /// The platform dependent lock used to synchronize continuations tracking. @usableFromInline - let locker: Locker = .init() + internal let locker: Locker = .init() /// The continuations stored with an associated key for all the suspended task that are waiting for access to resource. @usableFromInline - private(set) var continuations: OrderedDictionary = [:] + internal private(set) var continuations: + OrderedDictionary< + UUID, + Continuation + > = [:] /// Pool size for concurrent resource access. /// Has value provided during initialization incremented by one. @usableFromInline - let limit: UInt + internal let limit: UInt /// Current count of semaphore. /// Can have maximum value up to `limit`. @usableFromInline - private(set) var count: Int + internal private(set) var count: Int // MARK: Internal @@ -51,7 +58,7 @@ public actor AsyncSemaphore: AsyncObject { /// - continuation: The `continuation` to add. /// - key: The key in the map. @inlinable - func _addContinuation( + internal func _addContinuation( _ continuation: Continuation, withKey key: UUID ) { @@ -66,14 +73,14 @@ public actor AsyncSemaphore: AsyncObject { /// /// - Parameter key: The key in the map. @inlinable - func _removeContinuation(withKey key: UUID) { + internal func _removeContinuation(withKey key: UUID) { continuations.removeValue(forKey: key) _incrementCount() } /// Increments semaphore count within limit provided. @inlinable - func _incrementCount() { + internal func _incrementCount() { guard count < limit else { return } count += 1 } @@ -87,7 +94,7 @@ public actor AsyncSemaphore: AsyncObject { /// /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. @inlinable - nonisolated func _withPromisedContinuation() async throws { + internal nonisolated func _withPromisedContinuation() async throws { let key = UUID() try await Continuation.withCancellation(synchronizedWith: locker) { Task { [weak self] in @@ -102,7 +109,7 @@ public actor AsyncSemaphore: AsyncObject { /// Signals (increments) and releases a semaphore. @inlinable - func _signal() { + internal func _signal() { _incrementCount() guard !continuations.isEmpty else { return } let (_, continuation) = continuations.removeFirst() @@ -131,6 +138,7 @@ public actor AsyncSemaphore: AsyncObject { /// Increment the counting semaphore. /// If any previous task is waiting for access to semaphore, /// then the task is resumed from suspension. + @Sendable public nonisolated func signal() { Task { await _signal() } } diff --git a/Sources/AsyncObjects/CancellationSource.swift b/Sources/AsyncObjects/CancellationSource.swift index fbfaa78a..e3edd102 100644 --- a/Sources/AsyncObjects/CancellationSource.swift +++ b/Sources/AsyncObjects/CancellationSource.swift @@ -1,5 +1,3 @@ -import Foundation - /// An object that controls cooperative cancellation of multiple registered tasks and linked object registered tasks. /// /// An async event suspends tasks if current state is non-signaled and resumes execution when event is signalled. @@ -10,12 +8,32 @@ import Foundation /// and the cancellation event will be propagated to linked cancellation sources, /// which in turn cancels their registered tasks and further propagates cancellation. /// +/// ```swift +/// // create a root cancellation source +/// let source = CancellationSource() +/// // or a child cancellation source linked with multiple parents +/// let childSource = CancellationSource(linkedWith: source) +/// +/// // create task registered with cancellation source +/// let task = Task(cancellationSource: source) { +/// try await Task.sleep(nanoseconds: 1_000_000_000) +/// } +/// // or register already created task with cancellation source +/// source.register(task: task) +/// +/// // cancel all registered tasks and tasks registered +/// // in linked cancellation sources +/// source.cancel() +/// // or cancel after some time (fails if calling task cancelled) +/// try await source.cancel(afterNanoseconds: 1_000_000_000) +/// ``` +/// /// - Warning: Cancellation sources propagate cancellation event to other linked cancellation sources. /// In case of circular dependency between cancellation sources, app will go into infinite recursion. public actor CancellationSource { /// All the registered tasks for cooperative cancellation. @usableFromInline - private(set) var registeredTasks: [AnyHashable: () -> Void] = [:] + internal private(set) var registeredTasks: [AnyHashable: () -> Void] = [:] /// All the linked cancellation sources that cancellation event will be propagated. /// /// - TODO: Store weak reference for cancellation sources. @@ -23,7 +41,7 @@ public actor CancellationSource { /// private var linkedSources: NSHashTable = .weakObjects() /// ``` @usableFromInline - private(set) var linkedSources: [CancellationSource] = [] + internal private(set) var linkedSources: [CancellationSource] = [] // MARK: Internal @@ -31,7 +49,7 @@ public actor CancellationSource { /// /// - Parameter task: The task to register. @inlinable - func _add(task: Task) { + internal func _add(task: Task) { guard !task.isCancelled else { return } registeredTasks[task] = { task.cancel() } } @@ -40,7 +58,7 @@ public actor CancellationSource { /// /// - Parameter task: The task to remove. @inlinable - func _remove(task: Task) { + internal func _remove(task: Task) { registeredTasks.removeValue(forKey: task) } @@ -48,13 +66,13 @@ public actor CancellationSource { /// /// - Parameter task: The source to link. @inlinable - func _addSource(_ source: CancellationSource) { + internal func _addSource(_ source: CancellationSource) { linkedSources.append(source) } /// Propagate cancellation to linked cancellation sources. @inlinable - nonisolated func _propagateCancellation() async { + internal nonisolated func _propagateCancellation() async { await withTaskGroup(of: Void.self) { group in let linkedSources = await linkedSources linkedSources.forEach { group.addTask(operation: $0.cancel) } @@ -64,8 +82,7 @@ public actor CancellationSource { /// Trigger cancellation event, initiate cooperative cancellation of registered tasks /// and propagate cancellation to linked cancellation sources. - @Sendable - public func _cancel() async { + internal func _cancel() async { registeredTasks.forEach { $1() } registeredTasks = [:] await _propagateCancellation() @@ -175,6 +192,7 @@ public actor CancellationSource { /// If task completes before cancellation event is triggered, it is automatically unregistered. /// /// - Parameter task: The task to register. + @Sendable public nonisolated func register( task: Task ) { diff --git a/Sources/AsyncObjects/Future.swift b/Sources/AsyncObjects/Future.swift index a80b08b8..96166e50 100644 --- a/Sources/AsyncObjects/Future.swift +++ b/Sources/AsyncObjects/Future.swift @@ -14,6 +14,31 @@ import Foundation /// by using ``fulfill(with:)`` method. In the success case, /// the future’s downstream subscriber receives the element prior to the publishing stream finishing normally. /// If the result is an error, publishing terminates with that error. +/// +/// ```swift +/// // create a new unfulfilled future that is cancellable +/// let future = Future() +/// // or create a new unfulfilled future +/// // that is assured to be fulfilled +/// let future = Future() +/// // or create a future passing callback +/// // that fulfills the future +/// let future = Future { promise in +/// DispatchQueue.global(qos: .background) +/// .asyncAfter(deadline: .now() + 2) { +/// promise(.success(5)) +/// } +/// } +/// +/// // wait for future to be fulfilled with some value +/// // or cancelled with some error +/// let value = try await future.value +/// +/// // fulfill future with some value +/// await future.fulfill(producing: 5) +/// // or cancel future with error +/// await future.fulfill(throwing: CancellationError()) +/// ``` public actor Future { /// A type that represents a closure to invoke in the future, when an element or error is available. /// @@ -24,14 +49,16 @@ public actor Future { public typealias FutureResult = Result /// The suspended tasks continuation type. @usableFromInline - typealias Continuation = SafeContinuation> + internal typealias Continuation = SafeContinuation< + GlobalContinuation + > /// The platform dependent lock used to synchronize continuations tracking. @usableFromInline - let locker: Locker = .init() + internal let locker: Locker = .init() /// The continuations stored with an associated key for all the suspended task /// that are waiting for future to be fulfilled. @usableFromInline - private(set) var continuations: [UUID: Continuation] = [:] + internal private(set) var continuations: [UUID: Continuation] = [:] /// The underlying `Result` that indicates either future fulfilled or rejected. /// /// If future isn't fulfilled or rejected, the value is `nil`. @@ -43,7 +70,7 @@ public actor Future { /// - continuation: The `continuation` to add. /// - key: The key in the map. @inlinable - func _addContinuation( + internal func _addContinuation( _ continuation: Continuation, withKey key: UUID = .init() ) { @@ -56,7 +83,7 @@ public actor Future { /// any other variation of this methods. /// /// - Returns: The newly created future. - public init() { } + public init() {} /// Create an already fulfilled promise with the provided `Result`. /// @@ -67,6 +94,7 @@ public actor Future { self.result = result } + #if swift(>=5.7) /// Creates a future that invokes a promise closure when the publisher emits an element. /// /// - Parameters: @@ -76,11 +104,41 @@ public actor Future { /// - Returns: The newly created future. public init( attemptToFulfill: @Sendable @escaping ( - @escaping Future.Promise + @escaping Promise ) async -> Void - ) async { - Task { await attemptToFulfill(self.fulfill(with:)) } + ) { + self.init() + Task { + await attemptToFulfill { result in + Task { [weak self] in + await self?.fulfill(with: result) + } + } + } + } + #else + /// Creates a future that invokes a promise closure when the publisher emits an element. + /// + /// - Parameters: + /// - attemptToFulfill: A ``Future/Promise`` that the publisher invokes + /// when the publisher emits an element or terminates with an error. + /// + /// - Returns: The newly created future. + public convenience init( + attemptToFulfill: @Sendable @escaping ( + @escaping Promise + ) async -> Void + ) { + self.init() + Task { + await attemptToFulfill { result in + Task { [weak self] in + await self?.fulfill(with: result) + } + } + } } + #endif deinit { guard Failure.self is Error.Protocol else { return } @@ -133,7 +191,7 @@ extension Future where Failure == Never { /// /// - Returns: The value continuation is resumed with. @inlinable - nonisolated func _withPromisedContinuation() async -> Output { + internal nonisolated func _withPromisedContinuation() async -> Output { return await Continuation.with { continuation in Task { [weak self] in await self?._addContinuation(continuation) @@ -164,10 +222,10 @@ extension Future where Failure == Never { /// combining provided futures. public static func all( _ futures: [Future] - ) async -> Future<[Output], Failure> { + ) -> Future<[Output], Failure> { typealias IndexedOutput = (index: Int, value: Output) guard !futures.isEmpty else { return .init(with: .success([])) } - return await .init { promise in + return .init { promise in await withTaskGroup(of: IndexedOutput.self) { group in var result: [IndexedOutput] = [] result.reserveCapacity(futures.count) @@ -195,8 +253,8 @@ extension Future where Failure == Never { /// combining provided futures. public static func all( _ futures: Future... - ) async -> Future<[Output], Failure> { - return await Self.all(futures) + ) -> Future<[Output], Failure> { + return Self.all(futures) } /// Combines into a single future, for all futures to have settled. @@ -211,10 +269,10 @@ extension Future where Failure == Never { /// combining provided futures. public static func allSettled( _ futures: [Future] - ) async -> Future<[FutureResult], Never> { + ) -> Future<[FutureResult], Never> { typealias IndexedOutput = (index: Int, value: FutureResult) guard !futures.isEmpty else { return .init(with: .success([])) } - return await .init { promise in + return .init { promise in await withTaskGroup(of: IndexedOutput.self) { group in var result: [IndexedOutput] = [] result.reserveCapacity(futures.count) @@ -245,8 +303,8 @@ extension Future where Failure == Never { /// combining provided futures. public static func allSettled( _ futures: Future... - ) async -> Future<[FutureResult], Never> { - return await Self.allSettled(futures) + ) -> Future<[FutureResult], Never> { + return Self.allSettled(futures) } /// Takes multiple futures and, returns a single future that fulfills with the value @@ -260,8 +318,8 @@ extension Future where Failure == Never { /// if no future provided. public static func race( _ futures: [Future] - ) async -> Future { - return await .init { promise in + ) -> Future { + return .init { promise in await withTaskGroup(of: Output.self) { group in futures.forEach { future in group.addTask { await future.value } @@ -285,8 +343,8 @@ extension Future where Failure == Never { /// if no future provided. public static func race( _ futures: Future... - ) async -> Future { - return await Self.race(futures) + ) -> Future { + return Self.race(futures) } /// Takes multiple futures and, returns a single future that fulfills with the value as soon as one of the futures fulfills. @@ -299,8 +357,8 @@ extension Future where Failure == Never { /// or a forever pending future if no future provided. public static func any( _ futures: [Future] - ) async -> Future { - return await Self.race(futures) + ) -> Future { + return Self.race(futures) } /// Takes multiple futures and, returns a single future that fulfills with the value as soon as one of the futures fulfills. @@ -313,8 +371,8 @@ extension Future where Failure == Never { /// or a forever pending future if no future provided. public static func any( _ futures: Future... - ) async -> Future { - return await Self.any(futures) + ) -> Future { + return Self.any(futures) } } @@ -325,7 +383,7 @@ extension Future where Failure == Error { /// /// - Parameter key: The key in the map. @inlinable - func _removeContinuation(withKey key: UUID) { + internal func _removeContinuation(withKey key: UUID) { continuations.removeValue(forKey: key) } @@ -340,7 +398,8 @@ extension Future where Failure == Error { /// /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. @inlinable - nonisolated func _withPromisedContinuation() async throws -> Output { + internal nonisolated func _withPromisedContinuation() async throws -> Output + { let key = UUID() return try await Continuation.withCancellation( synchronizedWith: locker @@ -383,10 +442,10 @@ extension Future where Failure == Error { /// combining provided futures. public static func all( _ futures: [Future] - ) async -> Future<[Output], Failure> { + ) -> Future<[Output], Failure> { typealias IndexedOutput = (index: Int, value: Output) guard !futures.isEmpty else { return .init(with: .success([])) } - return await .init { promise in + return .init { promise in await withThrowingTaskGroup(of: IndexedOutput.self) { group in var result: [IndexedOutput] = [] result.reserveCapacity(futures.count) @@ -423,8 +482,8 @@ extension Future where Failure == Error { /// combining provided futures. public static func all( _ futures: Future... - ) async -> Future<[Output], Failure> { - return await Self.all(futures) + ) -> Future<[Output], Failure> { + return Self.all(futures) } /// Combines into a single future, for all futures to have settled (each may fulfill or reject). @@ -439,10 +498,10 @@ extension Future where Failure == Error { /// combining provided futures. public static func allSettled( _ futures: [Future] - ) async -> Future<[FutureResult], Never> { + ) -> Future<[FutureResult], Never> { typealias IndexedOutput = (index: Int, value: FutureResult) guard !futures.isEmpty else { return .init(with: .success([])) } - return await .init { promise in + return .init { promise in await withTaskGroup(of: IndexedOutput.self) { group in var result: [IndexedOutput] = [] result.reserveCapacity(futures.count) @@ -478,8 +537,8 @@ extension Future where Failure == Error { /// combining provided futures. public static func allSettled( _ futures: Future... - ) async -> Future<[FutureResult], Never> { - return await Self.allSettled(futures) + ) -> Future<[FutureResult], Never> { + return Self.allSettled(futures) } /// Takes multiple futures and, returns a single future that fulfills with the value @@ -495,8 +554,8 @@ extension Future where Failure == Error { /// if no future provided. public static func race( _ futures: [Future] - ) async -> Future { - return await .init { promise in + ) -> Future { + return .init { promise in await withThrowingTaskGroup(of: Output.self) { group in futures.forEach { future in group.addTask { try await future.value } @@ -526,8 +585,8 @@ extension Future where Failure == Error { /// if no future provided. public static func race( _ futures: Future... - ) async -> Future { - return await Self.race(futures) + ) -> Future { + return Self.race(futures) } /// Takes multiple futures and, returns a single future that fulfills with the value as soon as one of the futures fulfills. @@ -542,9 +601,9 @@ extension Future where Failure == Error { /// or a future rejected with `CancellationError` if no future provided. public static func any( _ futures: [Future] - ) async -> Future { + ) -> Future { guard !futures.isEmpty else { return .init(with: .cancelled) } - return await .init { promise in + return .init { promise in await withTaskGroup(of: FutureResult.self) { group in futures.forEach { future in group.addTask { @@ -589,8 +648,8 @@ extension Future where Failure == Error { /// or a future rejected with `CancellationError` if no future provided. public static func any( _ futures: Future... - ) async -> Future { - return await Self.any(futures) + ) -> Future { + return Self.any(futures) } } diff --git a/Sources/AsyncObjects/Locker.swift b/Sources/AsyncObjects/Locker.swift index 91ad8ab9..5445dfae 100644 --- a/Sources/AsyncObjects/Locker.swift +++ b/Sources/AsyncObjects/Locker.swift @@ -14,29 +14,45 @@ import Foundation /// This lock must be unlocked from the same thread that locked it, /// attempts to unlock from a different thread will cause an assertion aborting the process. /// This lock must not be accessed from multiple processes or threads via shared or multiply-mapped memory, -/// the lock implementation relies on the address of the lock value and owning process. +/// as the lock implementation relies on the address of the lock value and owning process. +/// +/// ```swift +/// // create a lock object to provide +/// // exclusive access to critical resources +/// let lock = Locker() +/// // perform critical mutually exclusive action +/// lock.perform { /* some action */ } +/// +/// // inside the critical action provided +/// // lock.perform can be called safely +/// lock.perform { +/// lock.perform { // works and runs with current context +/// /* some action */ +/// } +/// } +/// ``` public final class Locker: Equatable, Hashable, NSCopying, Sendable { #if canImport(Darwin) /// A type representing data for an unfair lock. - typealias Primitive = os_unfair_lock + internal typealias Primitive = os_unfair_lock #elseif canImport(Glibc) /// A type representing a MUTual EXclusion object. - typealias Primitive = pthread_mutex_t + internal typealias Primitive = pthread_mutex_t #elseif canImport(WinSDK) /// A type representing a slim reader/writer (SRW) lock. - typealias Primitive = SRWLOCK + internal typealias Primitive = SRWLOCK #endif /// Pointer type pointing to platform dependent lock primitive. - typealias PlatformLock = UnsafeMutablePointer + internal typealias PlatformLock = UnsafeMutablePointer /// Pointer to platform dependent lock primitive. - let platformLock: PlatformLock + internal let platformLock: PlatformLock /// Creates lock object with the provided pointer to platform dependent lock primitive. /// /// - Parameter platformLock: Pointer to platform dependent lock primitive. /// - Returns: The newly created lock object. - init(withLock platformLock: PlatformLock) { + internal init(withLock platformLock: PlatformLock) { self.platformLock = platformLock } @@ -72,10 +88,10 @@ public final class Locker: Equatable, Hashable, NSCopying, Sendable { /// - Warning: This method doesn't check if current thread /// has already acquired lock, and will cause runtime error /// if called repeatedly from same thread without releasing - /// with ``unlock()`` beforehand. Use the ``perform(_:)`` + /// with `_unlock()` beforehand. Use the ``perform(_:)`` /// method for safer handling of locking and unlocking. @available(*, noasync, message: "use perform(_:) instead") - public func lock() { + internal func _lock() { #if canImport(Darwin) os_unfair_lock_lock(platformLock) #elseif canImport(Glibc) @@ -95,11 +111,11 @@ public final class Locker: Equatable, Hashable, NSCopying, Sendable { /// /// - Warning: This method doesn't check if current thread /// has already acquired lock, and will cause runtime error - /// if called from a thread calling ``lock()`` beforehand. + /// if called from a thread calling `_lock()` beforehand. /// Use the ``perform(_:)`` method for safer handling /// of locking and unlocking. @available(*, noasync, message: "use perform(_:) instead") - public func unlock() { + internal func _unlock() { let threadDictionary = Thread.current.threadDictionary threadDictionary.removeObject(forKey: self) #if canImport(Darwin) @@ -120,9 +136,9 @@ public final class Locker: Equatable, Hashable, NSCopying, Sendable { /// - Warning: This method doesn't check if current thread /// has already acquired lock, and will cause runtime error /// if called repeatedly from same thread without releasing - /// with ``unlock()`` beforehand. Use the ``perform(_:)`` + /// with `_unlock()` beforehand. Use the ``perform(_:)`` /// method for safer handling of locking and unlocking. - public func lock() { + internal func _lock() { #if canImport(Darwin) os_unfair_lock_lock(platformLock) #elseif canImport(Glibc) @@ -142,10 +158,10 @@ public final class Locker: Equatable, Hashable, NSCopying, Sendable { /// /// - Warning: This method doesn't check if current thread /// has already acquired lock, and will cause runtime error - /// if called from a thread calling ``lock()`` beforehand. + /// if called from a thread calling `_lock()` beforehand. /// Use the ``perform(_:)`` method for safer handling /// of locking and unlocking. - public func unlock() { + internal func _unlock() { let threadDictionary = Thread.current.threadDictionary threadDictionary.removeObject(forKey: self) #if canImport(Darwin) @@ -166,7 +182,7 @@ public final class Locker: Equatable, Hashable, NSCopying, Sendable { /// /// This method checks if thread has already acquired lock and performs task /// without releasing the lock. This allows safer lock management eliminating - /// potential runtime errors, than manually invoking ``lock()`` and ``unlock()``. + /// potential runtime errors. /// /// - Parameter critical: The critical task to perform. /// - Returns: The result from the critical task. @@ -178,8 +194,8 @@ public final class Locker: Equatable, Hashable, NSCopying, Sendable { !(threadDictionary[self] as? Bool ?? false) else { return try critical() } - lock() - defer { unlock() } + _lock() + defer { _unlock() } return try critical() } diff --git a/Sources/AsyncObjects/SafeContinuation.swift b/Sources/AsyncObjects/SafeContinuation.swift index 4cef6115..5a9d3dcf 100644 --- a/Sources/AsyncObjects/SafeContinuation.swift +++ b/Sources/AsyncObjects/SafeContinuation.swift @@ -205,7 +205,7 @@ public final class SafeContinuation: Continuable { } extension SafeContinuation: ThrowingContinuable where C: ThrowingContinuable { - /// Suspends the current task, then calls the given operation with a``SafeContinuation`` + /// Suspends the current task, then calls the given operation with a ``SafeContinuation`` /// for the current task with a cancellation handler that’s immediately invoked if the current task is canceled. /// /// This differs from the operation cooperatively checking for cancellation and reacting to it in that diff --git a/Sources/AsyncObjects/TaskOperation.swift b/Sources/AsyncObjects/TaskOperation.swift index 2034c4e4..ce06dd0a 100644 --- a/Sources/AsyncObjects/TaskOperation.swift +++ b/Sources/AsyncObjects/TaskOperation.swift @@ -18,11 +18,14 @@ import Dispatch /// /// ```swift /// // create operation with async action -/// let operation = TaskOperation { try await Task.sleep(nanoseconds: 1_000_000_000) } +/// let operation = TaskOperation { +/// try await Task.sleep(nanoseconds: 1_000_000_000) +/// } /// // start operation to execute action /// operation.start() // operation.signal() /// -/// // wait for operation completion asynchrnously, fails only if task cancelled +/// // wait for operation completion asynchronously, +/// // fails only if task cancelled /// try await operation.wait() /// // or wait with some timeout /// try await operation.wait(forNanoseconds: 1_000_000_000) @@ -40,7 +43,7 @@ public final class TaskOperation: Operation, AsyncObject, /// The platform dependent lock used to /// synchronize data access and modifications. @usableFromInline - let locker: Locker + internal let locker: Locker /// A type representing a set of behaviors for the executed /// task type and task completion behavior. @@ -72,7 +75,7 @@ public final class TaskOperation: Operation, AsyncObject, /// Private store for boolean value indicating whether the operation is currently executing. @usableFromInline - var _isExecuting: Bool = false + internal var _isExecuting: Bool = false /// A Boolean value indicating whether the operation is currently executing. /// /// The value of this property is true if the operation is currently executing @@ -89,7 +92,7 @@ public final class TaskOperation: Operation, AsyncObject, /// Private store for boolean value indicating whether the operation has finished executing its task. @usableFromInline - var _isFinished: Bool = false + internal var _isFinished: Bool = false /// A Boolean value indicating whether the operation has finished executing its task. /// /// The value of this property is true if the operation is finished executing or cancelled @@ -196,7 +199,7 @@ public final class TaskOperation: Operation, AsyncObject, /// /// Must be called either when operation completes or cancelled. @inlinable - func _finish() { + internal func _finish() { isExecuting = false isFinished = true } @@ -204,10 +207,12 @@ public final class TaskOperation: Operation, AsyncObject, // MARK: AsyncObject /// The suspended tasks continuation type. @usableFromInline - typealias Continuation = SafeContinuation> + internal typealias Continuation = SafeContinuation< + GlobalContinuation + > /// The continuations stored with an associated key for all the suspended task that are waiting for operation completion. @usableFromInline - private(set) var continuations: [UUID: Continuation] = [:] + internal private(set) var continuations: [UUID: Continuation] = [:] /// Add continuation with the provided key in `continuations` map. /// @@ -215,7 +220,7 @@ public final class TaskOperation: Operation, AsyncObject, /// - continuation: The `continuation` to add. /// - key: The key in the map. @inlinable - func _addContinuation( + internal func _addContinuation( _ continuation: Continuation, withKey key: UUID ) { @@ -231,7 +236,7 @@ public final class TaskOperation: Operation, AsyncObject, /// /// - Parameter key: The key in the map. @inlinable - func _removeContinuation(withKey key: UUID) { + internal func _removeContinuation(withKey key: UUID) { locker.perform { continuations.removeValue(forKey: key) } } @@ -244,7 +249,7 @@ public final class TaskOperation: Operation, AsyncObject, /// /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. @inlinable - func _withPromisedContinuation() async throws { + internal func _withPromisedContinuation() async throws { let key = UUID() try await Continuation.withCancellation(synchronizedWith: locker) { Task { [weak self] in self?._removeContinuation(withKey: key) } diff --git a/Sources/AsyncObjects/TaskQueue.swift b/Sources/AsyncObjects/TaskQueue.swift index 713ffe4d..33923f37 100644 --- a/Sources/AsyncObjects/TaskQueue.swift +++ b/Sources/AsyncObjects/TaskQueue.swift @@ -11,6 +11,34 @@ import OrderedCollections /// or its non-throwing/non-cancellable version to run tasks concurrently. /// Additionally, you can provide priority of task and ``Flags`` /// to customize execution of submitted operation. +/// +/// ```swift +/// // create a queue with some priority processing async actions +/// let queue = TaskQueue() +/// // add operations to queue to be executed asynchronously +/// queue.addTask { +/// try await Task.sleep(nanoseconds: 1_000_000_000) +/// } +/// // or wait asynchronously for operation to be executed on queue +/// // the provided operation cancelled if invoking task cancelled +/// try await queue.exec { +/// try await Task.sleep(nanoseconds: 1_000_000_000) +/// } +/// +/// // provide additional flags for added operations +/// // execute operation as a barrier +/// queue.addTask(flags: .barrier) { +/// try await Task.sleep(nanoseconds: 1_000_000_000) +/// } +/// // execute operation as a detached task +/// queue.addTask(flags: .detached) { +/// try await Task.sleep(nanoseconds: 1_000_000_000) +/// } +/// // combine multiple flags for operation execution +/// queue.addTask(flags: [.barrier, .detached]) { +/// try await Task.sleep(nanoseconds: 1_000_000_000) +/// } +/// ``` public actor TaskQueue: AsyncObject { /// A set of behaviors for operations, such as its priority and whether to create a barrier /// or spawn a new detached task. @@ -55,7 +83,7 @@ public actor TaskQueue: AsyncObject { /// /// Returns `true` if either ``barrier`` or ``block`` flag provided. @usableFromInline - var isBlockEnabled: Bool { + internal var isBlockEnabled: Bool { return self.contains(.block) || self.contains(.barrier) } @@ -74,7 +102,7 @@ public actor TaskQueue: AsyncObject { /// - Returns: The determined priority of operation to be executed, /// based on provided flags. @usableFromInline - func choosePriority( + internal func choosePriority( fromContext context: TaskPriority?, andWork work: TaskPriority? ) -> TaskPriority? { @@ -105,7 +133,7 @@ public actor TaskQueue: AsyncObject { /// /// - Returns: Whether to suspend newly added task. @usableFromInline - func wait(forCurrent current: UInt) -> Bool { + internal func wait(forCurrent current: UInt) -> Bool { return self.contains(.barrier) ? current > 0 : false } @@ -132,14 +160,16 @@ public actor TaskQueue: AsyncObject { /// The suspended tasks continuation type. @usableFromInline - typealias Continuation = SafeContinuation> + internal typealias Continuation = SafeContinuation< + GlobalContinuation + > /// A mechanism to queue tasks in ``TaskQueue``, to be resumed when queue is freed /// and provided flags are satisfied. @usableFromInline - typealias QueuedContinuation = (value: Continuation, flags: Flags) + internal typealias QueuedContinuation = (value: Continuation, flags: Flags) /// The platform dependent lock used to synchronize continuations tracking. @usableFromInline - let locker: Locker = .init() + internal let locker: Locker = .init() /// The list of tasks currently queued and would be resumed one by one when current barrier task ends. @usableFromInline private(set) var queue: OrderedDictionary = [:] @@ -159,7 +189,7 @@ public actor TaskQueue: AsyncObject { /// - Parameter flags: The flags provided for new task. /// - Returns: Whether to wait to be resumed later. @inlinable - func _wait(whenFlags flags: Flags) -> Bool { + internal func _wait(whenFlags flags: Flags) -> Bool { return blocked || !queue.isEmpty || flags.wait(forCurrent: currentRunning) @@ -171,7 +201,7 @@ public actor TaskQueue: AsyncObject { /// - Returns: Whether queue is free to proceed scheduling other tasks. @inlinable @discardableResult - func _resumeQueuedContinuation( + internal func _resumeQueuedContinuation( _ continuation: QueuedContinuation ) -> Bool { currentRunning += 1 @@ -187,7 +217,7 @@ public actor TaskQueue: AsyncObject { /// - key: The key in the continuation queue. /// - continuation: The continuation and flags to add to queue. @inlinable - func _queueContinuation( + internal func _queueContinuation( atKey key: UUID = .init(), _ continuation: QueuedContinuation ) { @@ -203,7 +233,7 @@ public actor TaskQueue: AsyncObject { /// /// - Parameter key: The key in the continuation queue. @inlinable - func _dequeueContinuation(withKey key: UUID) { + internal func _dequeueContinuation(withKey key: UUID) { queue.removeValue(forKey: key) } @@ -213,7 +243,7 @@ public actor TaskQueue: AsyncObject { /// Updates the ``blocked`` flag and starts queued tasks /// in order of their addition if any tasks are queued. @inlinable - func _unblockQueue() { + internal func _unblockQueue() { blocked = false _resumeQueuedTasks() } @@ -224,7 +254,7 @@ public actor TaskQueue: AsyncObject { /// Updates the ``currentRunning`` count and starts /// queued tasks in order of their addition if any queued. @inlinable - func _signalCompletion() { + internal func _signalCompletion() { defer { _resumeQueuedTasks() } guard currentRunning > 0 else { return } currentRunning -= 1 @@ -233,7 +263,7 @@ public actor TaskQueue: AsyncObject { /// Resumes queued tasks when queue isn't blocked /// and operation flags preconditions satisfied. @inlinable - func _resumeQueuedTasks() { + internal func _resumeQueuedTasks() { while let (_, continuation) = queue.elements.first, !blocked, !continuation.flags.wait(forCurrent: currentRunning) @@ -254,7 +284,9 @@ public actor TaskQueue: AsyncObject { /// /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. @inlinable - nonisolated func _withPromisedContinuation(flags: Flags = []) async throws { + internal nonisolated func _withPromisedContinuation( + flags: Flags = [] + ) async throws { let key = UUID() try await Continuation.withCancellation( synchronizedWith: locker @@ -282,7 +314,7 @@ public actor TaskQueue: AsyncObject { /// - Returns: The result from provided operation. /// - Throws: `CancellationError` if cancelled, or error from provided operation. @inlinable - func _run( + internal func _run( with priority: TaskPriority?, flags: Flags, operation: @Sendable @escaping () async throws -> T @@ -316,7 +348,7 @@ public actor TaskQueue: AsyncObject { /// - Returns: The result from provided operation. /// - Throws: `CancellationError` if cancelled, or error from provided operation. @inlinable - func _runBlocking( + internal func _runBlocking( with priority: TaskPriority?, flags: Flags, operation: @Sendable @escaping () async throws -> T @@ -364,7 +396,7 @@ public actor TaskQueue: AsyncObject { /// - Throws: Error from provided operation or the cancellation handler. @discardableResult @inlinable - public func _execHelper( + internal func _execHelper( priority: TaskPriority? = nil, flags: Flags = [], operation: @Sendable @escaping () async throws -> T, @@ -418,6 +450,7 @@ public actor TaskQueue: AsyncObject { /// - Note: If task that added the operation to queue is cancelled, /// the provided operation also cancelled cooperatively if already started /// or the operation execution is skipped if only queued and not started. + @Sendable @discardableResult public func exec( priority: TaskPriority? = nil, @@ -444,6 +477,7 @@ public actor TaskQueue: AsyncObject { /// - operation: The non-throwing operation to perform. /// /// - Returns: The result from provided operation. + @Sendable @discardableResult public func exec( priority: TaskPriority? = nil, @@ -471,6 +505,7 @@ public actor TaskQueue: AsyncObject { /// - flags: Additional attributes to apply when executing the operation. /// For a list of possible values, see ``Flags``. /// - operation: The throwing operation to perform. + @Sendable public nonisolated func addTask( priority: TaskPriority? = nil, flags: Flags = [], @@ -497,6 +532,7 @@ public actor TaskQueue: AsyncObject { /// - flags: Additional attributes to apply when executing the operation. /// For a list of possible values, see ``Flags``. /// - operation: The non-throwing operation to perform. + @Sendable public nonisolated func addTask( priority: TaskPriority? = nil, flags: Flags = [], @@ -513,6 +549,7 @@ public actor TaskQueue: AsyncObject { /// Signalling on queue does nothing. /// Only added to satisfy ``AsyncObject`` requirements. + @Sendable public nonisolated func signal() { /* Do nothing */ } /// Waits for execution turn on queue. diff --git a/Sources/AsyncObjects/TaskTracker.swift b/Sources/AsyncObjects/TaskTracker.swift index db1da39e..c5e7f724 100644 --- a/Sources/AsyncObjects/TaskTracker.swift +++ b/Sources/AsyncObjects/TaskTracker.swift @@ -9,7 +9,7 @@ /// Do not keep strong reference of this object, as then object won't /// deallocate as soon asynchronous operations and their created /// unstructured tasks complete. -final class TaskTracker: Sendable { +internal final class TaskTracker: Sendable { /// The tracker associated with current task. /// /// Use the `withValue` method to assign a tracker to asynchronous operation. @@ -21,7 +21,7 @@ final class TaskTracker: Sendable { /// deallocate as soon asynchronous operations and their created /// unstructured tasks complete. @TaskLocal - static var current: TaskTracker? + internal static var current: TaskTracker? /// The action to complete when task and all its created unstructured tasks complete. private let fire: @Sendable () -> Void @@ -39,7 +39,7 @@ final class TaskTracker: Sendable { /// Do not keep strong reference of this object, as then object won't /// deallocate as soon asynchronous operations and their created /// unstructured tasks complete. - init(onComplete fire: @Sendable @escaping () -> Void) { + internal init(onComplete fire: @Sendable @escaping () -> Void) { self.fire = fire } diff --git a/Tests/AsyncObjectsTests/CancellationSourceTests.swift b/Tests/AsyncObjectsTests/CancellationSourceTests.swift index 97cf1227..8c001fe2 100644 --- a/Tests/AsyncObjectsTests/CancellationSourceTests.swift +++ b/Tests/AsyncObjectsTests/CancellationSourceTests.swift @@ -10,6 +10,7 @@ class CancellationSourceTests: XCTestCase { try await Self.sleep(seconds: 1) } source.register(task: task) + try await Self.sleep(forSeconds: 0.001) source.cancel() try await Self.sleep(forSeconds: 0.001) XCTAssertTrue(task.isCancelled) diff --git a/Tests/AsyncObjectsTests/NonThrowingFutureTests.swift b/Tests/AsyncObjectsTests/NonThrowingFutureTests.swift index 6df1b53f..8af5134b 100644 --- a/Tests/AsyncObjectsTests/NonThrowingFutureTests.swift +++ b/Tests/AsyncObjectsTests/NonThrowingFutureTests.swift @@ -27,7 +27,7 @@ class NonThrowingFutureTests: XCTestCase { } func testFutureFulfilledWithAttemptClosure() async throws { - let future = await Future { promise in + let future = Future { promise in DispatchQueue.global(qos: .background) .asyncAfter(deadline: .now() + 2) { promise(.success(5)) @@ -41,7 +41,7 @@ class NonThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.all(future1, future3, future2) + let allFuture = Future.all(future1, future3, future2) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { @@ -71,7 +71,7 @@ class NonThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.allSettled(future1, future2, future3) + let allFuture = Future.allSettled(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { @@ -108,7 +108,7 @@ class NonThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.race(future1, future2, future3) + let allFuture = Future.race(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { @@ -140,7 +140,7 @@ class NonThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.any(future1, future2, future3) + let allFuture = Future.any(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { @@ -169,13 +169,13 @@ class NonThrowingFutureTests: XCTestCase { } func testConstructingAllFutureFromZeroFutures() async { - let future = await Future.all() + let future = Future.all() let value = await future.value XCTAssertTrue(value.isEmpty) } func testConstructingAllSettledFutureFromZeroFutures() async { - let future = await Future.allSettled() + let future = Future.allSettled() let value = await future.value XCTAssertTrue(value.isEmpty) } @@ -189,7 +189,7 @@ class NonThrowingFutureTests: XCTestCase { func testFutureAsyncInitializerDuration() async throws { await Self.checkExecInterval(durationInSeconds: 0) { - let _ = await Future { promise in + let _ = Future { promise in try! await Self.sleep(seconds: 1) promise(.success(5)) } diff --git a/Tests/AsyncObjectsTests/ThrowingFutureTests.swift b/Tests/AsyncObjectsTests/ThrowingFutureTests.swift index ccb1ed0e..8e8bdf7d 100644 --- a/Tests/AsyncObjectsTests/ThrowingFutureTests.swift +++ b/Tests/AsyncObjectsTests/ThrowingFutureTests.swift @@ -72,7 +72,7 @@ class ThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.all(future1, future2, future3) + let allFuture = Future.all(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { @@ -102,7 +102,7 @@ class ThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.all(future1, future2, future3) + let allFuture = Future.all(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { @@ -140,7 +140,7 @@ class ThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.allSettled(future1, future2, future3) + let allFuture = Future.allSettled(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { @@ -177,7 +177,7 @@ class ThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.allSettled(future1, future2, future3) + let allFuture = Future.allSettled(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { @@ -219,7 +219,7 @@ class ThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.race(future1, future2, future3) + let allFuture = Future.race(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { @@ -251,7 +251,7 @@ class ThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.race(future1, future2, future3) + let allFuture = Future.race(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { @@ -289,7 +289,7 @@ class ThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.any(future1, future2, future3) + let allFuture = Future.any(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { @@ -321,7 +321,7 @@ class ThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.any(future1, future2, future3) + let allFuture = Future.any(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { @@ -353,7 +353,7 @@ class ThrowingFutureTests: XCTestCase { let future1 = Future() let future2 = Future() let future3 = Future() - let allFuture = await Future.any(future1, future2, future3) + let allFuture = Future.any(future1, future2, future3) try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { @@ -388,7 +388,7 @@ class ThrowingFutureTests: XCTestCase { } func testConstructingAnyFutureFromZeroFutures() async { - let future = await Future.any() + let future = Future.any() let result = await future.result switch result { case .failure(let error): @@ -398,13 +398,13 @@ class ThrowingFutureTests: XCTestCase { } func testConstructingAllFutureFromZeroFutures() async throws { - let future = await Future.all() + let future = Future.all() let value = try await future.value XCTAssertTrue(value.isEmpty) } func testConstructingAllSettledFutureFromZeroFutures() async throws { - let future = await Future.allSettled() + let future = Future.allSettled() let value = await future.value XCTAssertTrue(value.isEmpty) } From 6b705668c1565cf232e5fa352ce13c93a9525b0a Mon Sep 17 00:00:00 2001 From: Soumya Ranjan Mahunt Date: Sun, 11 Sep 2022 22:46:01 +0530 Subject: [PATCH 05/14] refactor: remove logic duplication for continuation management --- AsyncObjects.xcodeproj/project.pbxproj | 895 +++++++++--------- .../AsyncObjects/AsyncCountdownEvent.swift | 24 +- Sources/AsyncObjects/AsyncEvent.swift | 24 +- Sources/AsyncObjects/AsyncSemaphore.swift | 24 +- .../{ => Continuation}/Continuable.swift | 188 +--- .../Continuation/ContinuableCollection.swift | 79 ++ .../Continuation/GlobalContinuation.swift | 132 +++ .../{ => Continuation}/SafeContinuation.swift | 178 +--- .../SynchronizedContinuable.swift | 189 ++++ .../AsyncObjects/{ => Extensions}/Task.swift | 50 - .../AsyncObjects/Extensions/TaskGroup.swift | 49 + Sources/AsyncObjects/Future.swift | 38 +- Sources/AsyncObjects/Locks/Exclusible.swift | 23 + Sources/AsyncObjects/{ => Locks}/Locker.swift | 57 +- Sources/AsyncObjects/TaskOperation.swift | 26 +- Sources/AsyncObjects/TaskQueue.swift | 2 +- .../NonThrowingFutureTests.swift | 24 +- .../SafeContinuationTests.swift | 29 +- .../StandardLibraryTests.swift | 1 + .../ThrowingFutureTests.swift | 40 +- 20 files changed, 1122 insertions(+), 950 deletions(-) rename Sources/AsyncObjects/{ => Continuation}/Continuable.swift (51%) create mode 100644 Sources/AsyncObjects/Continuation/ContinuableCollection.swift create mode 100644 Sources/AsyncObjects/Continuation/GlobalContinuation.swift rename Sources/AsyncObjects/{ => Continuation}/SafeContinuation.swift (51%) create mode 100644 Sources/AsyncObjects/Continuation/SynchronizedContinuable.swift rename Sources/AsyncObjects/{ => Extensions}/Task.swift (52%) create mode 100644 Sources/AsyncObjects/Extensions/TaskGroup.swift create mode 100644 Sources/AsyncObjects/Locks/Exclusible.swift rename Sources/AsyncObjects/{ => Locks}/Locker.swift (75%) diff --git a/AsyncObjects.xcodeproj/project.pbxproj b/AsyncObjects.xcodeproj/project.pbxproj index 9995bf5b..2e33008b 100644 --- a/AsyncObjects.xcodeproj/project.pbxproj +++ b/AsyncObjects.xcodeproj/project.pbxproj @@ -9,11 +9,11 @@ /* Begin PBXAggregateTarget section */ asyncobjects::AsyncObjectsPackageTests::ProductTarget /* AsyncObjectsPackageTests */ = { isa = PBXAggregateTarget; - buildConfigurationList = OBJ_132 /* Build configuration list for PBXAggregateTarget "AsyncObjectsPackageTests" */; + buildConfigurationList = OBJ_145 /* Build configuration list for PBXAggregateTarget "AsyncObjectsPackageTests" */; buildPhases = ( ); dependencies = ( - OBJ_135 /* PBXTargetDependency */, + OBJ_148 /* PBXTargetDependency */, ); name = AsyncObjectsPackageTests; productName = AsyncObjectsPackageTests; @@ -21,191 +21,201 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ - 0663FA4A7B71E49448BE9547 /* AsyncObjects.docc in Sources */ = {isa = PBXBuildFile; fileRef = B7A072784A06B1A1C700CC40 /* AsyncObjects.docc */; }; - OBJ_108 /* AsyncCountdownEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* AsyncCountdownEvent.swift */; }; - OBJ_109 /* AsyncEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* AsyncEvent.swift */; }; - OBJ_110 /* AsyncObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* AsyncObject.swift */; }; - OBJ_111 /* AsyncSemaphore.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* AsyncSemaphore.swift */; }; - OBJ_112 /* CancellationSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* CancellationSource.swift */; }; - OBJ_113 /* Continuable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* Continuable.swift */; }; - OBJ_114 /* Future.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* Future.swift */; }; - OBJ_115 /* Locker.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_18 /* Locker.swift */; }; - OBJ_116 /* SafeContinuation.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_19 /* SafeContinuation.swift */; }; - OBJ_117 /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_20 /* Task.swift */; }; - OBJ_118 /* TaskOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_21 /* TaskOperation.swift */; }; - OBJ_119 /* TaskQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_22 /* TaskQueue.swift */; }; - OBJ_120 /* TaskTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_23 /* TaskTracker.swift */; }; - OBJ_122 /* OrderedCollections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = swift-collections::OrderedCollections::Product /* OrderedCollections.framework */; }; - OBJ_130 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; }; - OBJ_141 /* AsyncCountdownEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_26 /* AsyncCountdownEventTests.swift */; }; - OBJ_142 /* AsyncEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_27 /* AsyncEventTests.swift */; }; - OBJ_143 /* AsyncObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_28 /* AsyncObjectTests.swift */; }; - OBJ_144 /* AsyncSemaphoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_29 /* AsyncSemaphoreTests.swift */; }; - OBJ_145 /* CancellationSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_30 /* CancellationSourceTests.swift */; }; - OBJ_146 /* LockerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_31 /* LockerTests.swift */; }; - OBJ_147 /* NonThrowingFutureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_32 /* NonThrowingFutureTests.swift */; }; - OBJ_148 /* SafeContinuationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_33 /* SafeContinuationTests.swift */; }; - OBJ_149 /* StandardLibraryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_34 /* StandardLibraryTests.swift */; }; - OBJ_150 /* TaskOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_35 /* TaskOperationTests.swift */; }; - OBJ_151 /* TaskQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_36 /* TaskQueueTests.swift */; }; - OBJ_152 /* ThrowingFutureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_37 /* ThrowingFutureTests.swift */; }; - OBJ_153 /* XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_38 /* XCTestCase.swift */; }; - OBJ_155 /* AsyncObjects.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = asyncobjects::AsyncObjects::Product /* AsyncObjects.framework */; }; - OBJ_156 /* OrderedCollections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = swift-collections::OrderedCollections::Product /* OrderedCollections.framework */; }; - OBJ_163 /* _HashTable+Bucket.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_45 /* _HashTable+Bucket.swift */; }; - OBJ_164 /* _HashTable+BucketIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_46 /* _HashTable+BucketIterator.swift */; }; - OBJ_165 /* _HashTable+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_47 /* _HashTable+Constants.swift */; }; - OBJ_166 /* _HashTable+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_48 /* _HashTable+CustomStringConvertible.swift */; }; - OBJ_167 /* _HashTable+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_49 /* _HashTable+Testing.swift */; }; - OBJ_168 /* _HashTable+UnsafeHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_50 /* _HashTable+UnsafeHandle.swift */; }; - OBJ_169 /* _HashTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_51 /* _HashTable.swift */; }; - OBJ_170 /* _Hashtable+Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_52 /* _Hashtable+Header.swift */; }; - OBJ_171 /* OrderedDictionary+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_54 /* OrderedDictionary+Codable.swift */; }; - OBJ_172 /* OrderedDictionary+CustomDebugStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_55 /* OrderedDictionary+CustomDebugStringConvertible.swift */; }; - OBJ_173 /* OrderedDictionary+CustomReflectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_56 /* OrderedDictionary+CustomReflectable.swift */; }; - OBJ_174 /* OrderedDictionary+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_57 /* OrderedDictionary+CustomStringConvertible.swift */; }; - OBJ_175 /* OrderedDictionary+Deprecations.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_58 /* OrderedDictionary+Deprecations.swift */; }; - OBJ_176 /* OrderedDictionary+Elements+SubSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_59 /* OrderedDictionary+Elements+SubSequence.swift */; }; - OBJ_177 /* OrderedDictionary+Elements.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_60 /* OrderedDictionary+Elements.swift */; }; - OBJ_178 /* OrderedDictionary+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_61 /* OrderedDictionary+Equatable.swift */; }; - OBJ_179 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_62 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift */; }; - OBJ_180 /* OrderedDictionary+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_63 /* OrderedDictionary+Hashable.swift */; }; - OBJ_181 /* OrderedDictionary+Initializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_64 /* OrderedDictionary+Initializers.swift */; }; - OBJ_182 /* OrderedDictionary+Invariants.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_65 /* OrderedDictionary+Invariants.swift */; }; - OBJ_183 /* OrderedDictionary+Partial MutableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_66 /* OrderedDictionary+Partial MutableCollection.swift */; }; - OBJ_184 /* OrderedDictionary+Partial RangeReplaceableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_67 /* OrderedDictionary+Partial RangeReplaceableCollection.swift */; }; - OBJ_185 /* OrderedDictionary+Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_68 /* OrderedDictionary+Sequence.swift */; }; - OBJ_186 /* OrderedDictionary+Values.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_69 /* OrderedDictionary+Values.swift */; }; - OBJ_187 /* OrderedDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_70 /* OrderedDictionary.swift */; }; - OBJ_188 /* OrderedSet+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_72 /* OrderedSet+Codable.swift */; }; - OBJ_189 /* OrderedSet+CustomDebugStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_73 /* OrderedSet+CustomDebugStringConvertible.swift */; }; - OBJ_190 /* OrderedSet+CustomReflectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_74 /* OrderedSet+CustomReflectable.swift */; }; - OBJ_191 /* OrderedSet+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_75 /* OrderedSet+CustomStringConvertible.swift */; }; - OBJ_192 /* OrderedSet+Diffing.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_76 /* OrderedSet+Diffing.swift */; }; - OBJ_193 /* OrderedSet+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_77 /* OrderedSet+Equatable.swift */; }; - OBJ_194 /* OrderedSet+ExpressibleByArrayLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_78 /* OrderedSet+ExpressibleByArrayLiteral.swift */; }; - OBJ_195 /* OrderedSet+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_79 /* OrderedSet+Hashable.swift */; }; - OBJ_196 /* OrderedSet+Initializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_80 /* OrderedSet+Initializers.swift */; }; - OBJ_197 /* OrderedSet+Insertions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_81 /* OrderedSet+Insertions.swift */; }; - OBJ_198 /* OrderedSet+Invariants.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_82 /* OrderedSet+Invariants.swift */; }; - OBJ_199 /* OrderedSet+Partial MutableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_83 /* OrderedSet+Partial MutableCollection.swift */; }; - OBJ_200 /* OrderedSet+Partial RangeReplaceableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_84 /* OrderedSet+Partial RangeReplaceableCollection.swift */; }; - OBJ_201 /* OrderedSet+Partial SetAlgebra+Basics.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_85 /* OrderedSet+Partial SetAlgebra+Basics.swift */; }; - OBJ_202 /* OrderedSet+Partial SetAlgebra+Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_86 /* OrderedSet+Partial SetAlgebra+Operations.swift */; }; - OBJ_203 /* OrderedSet+Partial SetAlgebra+Predicates.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_87 /* OrderedSet+Partial SetAlgebra+Predicates.swift */; }; - OBJ_204 /* OrderedSet+RandomAccessCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_88 /* OrderedSet+RandomAccessCollection.swift */; }; - OBJ_205 /* OrderedSet+ReserveCapacity.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_89 /* OrderedSet+ReserveCapacity.swift */; }; - OBJ_206 /* OrderedSet+SubSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_90 /* OrderedSet+SubSequence.swift */; }; - OBJ_207 /* OrderedSet+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_91 /* OrderedSet+Testing.swift */; }; - OBJ_208 /* OrderedSet+UnorderedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_92 /* OrderedSet+UnorderedView.swift */; }; - OBJ_209 /* OrderedSet+UnstableInternals.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_93 /* OrderedSet+UnstableInternals.swift */; }; - OBJ_210 /* OrderedSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_94 /* OrderedSet.swift */; }; - OBJ_211 /* RandomAccessCollection+Offsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_96 /* RandomAccessCollection+Offsets.swift */; }; - OBJ_212 /* _UnsafeBitset.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_97 /* _UnsafeBitset.swift */; }; - OBJ_219 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_98 /* Package.swift */; }; + DFEF4D3A44700284C230A854 /* AsyncObjects.docc in Sources */ = {isa = PBXBuildFile; fileRef = EB6E2F9D4FEF6DDD65D0B251 /* AsyncObjects.docc */; }; + OBJ_116 /* AsyncCountdownEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* AsyncCountdownEvent.swift */; }; + OBJ_117 /* AsyncEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* AsyncEvent.swift */; }; + OBJ_118 /* AsyncObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* AsyncObject.swift */; }; + OBJ_119 /* AsyncSemaphore.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* AsyncSemaphore.swift */; }; + OBJ_120 /* CancellationSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* CancellationSource.swift */; }; + OBJ_121 /* Continuable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* Continuable.swift */; }; + OBJ_122 /* ContinuableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_18 /* ContinuableCollection.swift */; }; + OBJ_123 /* GlobalContinuation.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_19 /* GlobalContinuation.swift */; }; + OBJ_124 /* SafeContinuation.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_20 /* SafeContinuation.swift */; }; + OBJ_125 /* SynchronizedContinuable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_21 /* SynchronizedContinuable.swift */; }; + OBJ_126 /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_23 /* Task.swift */; }; + OBJ_127 /* TaskGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_24 /* TaskGroup.swift */; }; + OBJ_128 /* Future.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_25 /* Future.swift */; }; + OBJ_129 /* Exclusible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_27 /* Exclusible.swift */; }; + OBJ_130 /* Locker.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_28 /* Locker.swift */; }; + OBJ_131 /* TaskOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_29 /* TaskOperation.swift */; }; + OBJ_132 /* TaskQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_30 /* TaskQueue.swift */; }; + OBJ_133 /* TaskTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_31 /* TaskTracker.swift */; }; + OBJ_135 /* OrderedCollections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = swift-collections::OrderedCollections::Product /* OrderedCollections.framework */; }; + OBJ_143 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; }; + OBJ_154 /* AsyncCountdownEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_34 /* AsyncCountdownEventTests.swift */; }; + OBJ_155 /* AsyncEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_35 /* AsyncEventTests.swift */; }; + OBJ_156 /* AsyncObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_36 /* AsyncObjectTests.swift */; }; + OBJ_157 /* AsyncSemaphoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_37 /* AsyncSemaphoreTests.swift */; }; + OBJ_158 /* CancellationSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_38 /* CancellationSourceTests.swift */; }; + OBJ_159 /* LockerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_39 /* LockerTests.swift */; }; + OBJ_160 /* NonThrowingFutureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_40 /* NonThrowingFutureTests.swift */; }; + OBJ_161 /* SafeContinuationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_41 /* SafeContinuationTests.swift */; }; + OBJ_162 /* StandardLibraryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_42 /* StandardLibraryTests.swift */; }; + OBJ_163 /* TaskOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_43 /* TaskOperationTests.swift */; }; + OBJ_164 /* TaskQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_44 /* TaskQueueTests.swift */; }; + OBJ_165 /* ThrowingFutureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_45 /* ThrowingFutureTests.swift */; }; + OBJ_166 /* XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_46 /* XCTestCase.swift */; }; + OBJ_168 /* AsyncObjects.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = asyncobjects::AsyncObjects::Product /* AsyncObjects.framework */; }; + OBJ_169 /* OrderedCollections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = swift-collections::OrderedCollections::Product /* OrderedCollections.framework */; }; + OBJ_176 /* _HashTable+Bucket.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_53 /* _HashTable+Bucket.swift */; }; + OBJ_177 /* _HashTable+BucketIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_54 /* _HashTable+BucketIterator.swift */; }; + OBJ_178 /* _HashTable+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_55 /* _HashTable+Constants.swift */; }; + OBJ_179 /* _HashTable+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_56 /* _HashTable+CustomStringConvertible.swift */; }; + OBJ_180 /* _HashTable+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_57 /* _HashTable+Testing.swift */; }; + OBJ_181 /* _HashTable+UnsafeHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_58 /* _HashTable+UnsafeHandle.swift */; }; + OBJ_182 /* _HashTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_59 /* _HashTable.swift */; }; + OBJ_183 /* _Hashtable+Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_60 /* _Hashtable+Header.swift */; }; + OBJ_184 /* OrderedDictionary+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_62 /* OrderedDictionary+Codable.swift */; }; + OBJ_185 /* OrderedDictionary+CustomDebugStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_63 /* OrderedDictionary+CustomDebugStringConvertible.swift */; }; + OBJ_186 /* OrderedDictionary+CustomReflectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_64 /* OrderedDictionary+CustomReflectable.swift */; }; + OBJ_187 /* OrderedDictionary+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_65 /* OrderedDictionary+CustomStringConvertible.swift */; }; + OBJ_188 /* OrderedDictionary+Deprecations.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_66 /* OrderedDictionary+Deprecations.swift */; }; + OBJ_189 /* OrderedDictionary+Elements+SubSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_67 /* OrderedDictionary+Elements+SubSequence.swift */; }; + OBJ_190 /* OrderedDictionary+Elements.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_68 /* OrderedDictionary+Elements.swift */; }; + OBJ_191 /* OrderedDictionary+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_69 /* OrderedDictionary+Equatable.swift */; }; + OBJ_192 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_70 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift */; }; + OBJ_193 /* OrderedDictionary+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_71 /* OrderedDictionary+Hashable.swift */; }; + OBJ_194 /* OrderedDictionary+Initializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_72 /* OrderedDictionary+Initializers.swift */; }; + OBJ_195 /* OrderedDictionary+Invariants.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_73 /* OrderedDictionary+Invariants.swift */; }; + OBJ_196 /* OrderedDictionary+Partial MutableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_74 /* OrderedDictionary+Partial MutableCollection.swift */; }; + OBJ_197 /* OrderedDictionary+Partial RangeReplaceableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_75 /* OrderedDictionary+Partial RangeReplaceableCollection.swift */; }; + OBJ_198 /* OrderedDictionary+Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_76 /* OrderedDictionary+Sequence.swift */; }; + OBJ_199 /* OrderedDictionary+Values.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_77 /* OrderedDictionary+Values.swift */; }; + OBJ_200 /* OrderedDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_78 /* OrderedDictionary.swift */; }; + OBJ_201 /* OrderedSet+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_80 /* OrderedSet+Codable.swift */; }; + OBJ_202 /* OrderedSet+CustomDebugStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_81 /* OrderedSet+CustomDebugStringConvertible.swift */; }; + OBJ_203 /* OrderedSet+CustomReflectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_82 /* OrderedSet+CustomReflectable.swift */; }; + OBJ_204 /* OrderedSet+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_83 /* OrderedSet+CustomStringConvertible.swift */; }; + OBJ_205 /* OrderedSet+Diffing.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_84 /* OrderedSet+Diffing.swift */; }; + OBJ_206 /* OrderedSet+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_85 /* OrderedSet+Equatable.swift */; }; + OBJ_207 /* OrderedSet+ExpressibleByArrayLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_86 /* OrderedSet+ExpressibleByArrayLiteral.swift */; }; + OBJ_208 /* OrderedSet+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_87 /* OrderedSet+Hashable.swift */; }; + OBJ_209 /* OrderedSet+Initializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_88 /* OrderedSet+Initializers.swift */; }; + OBJ_210 /* OrderedSet+Insertions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_89 /* OrderedSet+Insertions.swift */; }; + OBJ_211 /* OrderedSet+Invariants.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_90 /* OrderedSet+Invariants.swift */; }; + OBJ_212 /* OrderedSet+Partial MutableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_91 /* OrderedSet+Partial MutableCollection.swift */; }; + OBJ_213 /* OrderedSet+Partial RangeReplaceableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_92 /* OrderedSet+Partial RangeReplaceableCollection.swift */; }; + OBJ_214 /* OrderedSet+Partial SetAlgebra+Basics.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_93 /* OrderedSet+Partial SetAlgebra+Basics.swift */; }; + OBJ_215 /* OrderedSet+Partial SetAlgebra+Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_94 /* OrderedSet+Partial SetAlgebra+Operations.swift */; }; + OBJ_216 /* OrderedSet+Partial SetAlgebra+Predicates.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_95 /* OrderedSet+Partial SetAlgebra+Predicates.swift */; }; + OBJ_217 /* OrderedSet+RandomAccessCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_96 /* OrderedSet+RandomAccessCollection.swift */; }; + OBJ_218 /* OrderedSet+ReserveCapacity.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_97 /* OrderedSet+ReserveCapacity.swift */; }; + OBJ_219 /* OrderedSet+SubSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_98 /* OrderedSet+SubSequence.swift */; }; + OBJ_220 /* OrderedSet+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_99 /* OrderedSet+Testing.swift */; }; + OBJ_221 /* OrderedSet+UnorderedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_100 /* OrderedSet+UnorderedView.swift */; }; + OBJ_222 /* OrderedSet+UnstableInternals.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_101 /* OrderedSet+UnstableInternals.swift */; }; + OBJ_223 /* OrderedSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_102 /* OrderedSet.swift */; }; + OBJ_224 /* RandomAccessCollection+Offsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_104 /* RandomAccessCollection+Offsets.swift */; }; + OBJ_225 /* _UnsafeBitset.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_105 /* _UnsafeBitset.swift */; }; + OBJ_232 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_106 /* Package.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - B7A072784A06B1A1C700CC40 /* AsyncObjects.docc */ = {isa = PBXFileReference; includeInIndex = 1; path = AsyncObjects.docc; sourceTree = ""; }; + EB6E2F9D4FEF6DDD65D0B251 /* AsyncObjects.docc */ = {isa = PBXFileReference; includeInIndex = 1; path = AsyncObjects.docc; sourceTree = ""; }; + OBJ_100 /* OrderedSet+UnorderedView.swift */ = {isa = PBXFileReference; path = "OrderedSet+UnorderedView.swift"; sourceTree = ""; }; + OBJ_101 /* OrderedSet+UnstableInternals.swift */ = {isa = PBXFileReference; path = "OrderedSet+UnstableInternals.swift"; sourceTree = ""; }; + OBJ_102 /* OrderedSet.swift */ = {isa = PBXFileReference; path = OrderedSet.swift; sourceTree = ""; }; + OBJ_104 /* RandomAccessCollection+Offsets.swift */ = {isa = PBXFileReference; path = "RandomAccessCollection+Offsets.swift"; sourceTree = ""; }; + OBJ_105 /* _UnsafeBitset.swift */ = {isa = PBXFileReference; path = _UnsafeBitset.swift; sourceTree = ""; }; + OBJ_106 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; name = Package.swift; path = "/Users/soumyaranjanmahunt/Documents/personal_projs/AsyncObjects/.build/checkouts/swift-collections/Package.swift"; sourceTree = ""; }; OBJ_11 /* AsyncCountdownEvent.swift */ = {isa = PBXFileReference; path = AsyncCountdownEvent.swift; sourceTree = ""; }; OBJ_12 /* AsyncEvent.swift */ = {isa = PBXFileReference; path = AsyncEvent.swift; sourceTree = ""; }; OBJ_13 /* AsyncObject.swift */ = {isa = PBXFileReference; path = AsyncObject.swift; sourceTree = ""; }; OBJ_14 /* AsyncSemaphore.swift */ = {isa = PBXFileReference; path = AsyncSemaphore.swift; sourceTree = ""; }; OBJ_15 /* CancellationSource.swift */ = {isa = PBXFileReference; path = CancellationSource.swift; sourceTree = ""; }; - OBJ_16 /* Continuable.swift */ = {isa = PBXFileReference; path = Continuable.swift; sourceTree = ""; }; - OBJ_17 /* Future.swift */ = {isa = PBXFileReference; path = Future.swift; sourceTree = ""; }; - OBJ_18 /* Locker.swift */ = {isa = PBXFileReference; path = Locker.swift; sourceTree = ""; }; - OBJ_19 /* SafeContinuation.swift */ = {isa = PBXFileReference; path = SafeContinuation.swift; sourceTree = ""; }; - OBJ_20 /* Task.swift */ = {isa = PBXFileReference; path = Task.swift; sourceTree = ""; }; - OBJ_21 /* TaskOperation.swift */ = {isa = PBXFileReference; path = TaskOperation.swift; sourceTree = ""; }; - OBJ_22 /* TaskQueue.swift */ = {isa = PBXFileReference; path = TaskQueue.swift; sourceTree = ""; }; - OBJ_23 /* TaskTracker.swift */ = {isa = PBXFileReference; path = TaskTracker.swift; sourceTree = ""; }; - OBJ_26 /* AsyncCountdownEventTests.swift */ = {isa = PBXFileReference; path = AsyncCountdownEventTests.swift; sourceTree = ""; }; - OBJ_27 /* AsyncEventTests.swift */ = {isa = PBXFileReference; path = AsyncEventTests.swift; sourceTree = ""; }; - OBJ_28 /* AsyncObjectTests.swift */ = {isa = PBXFileReference; path = AsyncObjectTests.swift; sourceTree = ""; }; - OBJ_29 /* AsyncSemaphoreTests.swift */ = {isa = PBXFileReference; path = AsyncSemaphoreTests.swift; sourceTree = ""; }; - OBJ_30 /* CancellationSourceTests.swift */ = {isa = PBXFileReference; path = CancellationSourceTests.swift; sourceTree = ""; }; - OBJ_31 /* LockerTests.swift */ = {isa = PBXFileReference; path = LockerTests.swift; sourceTree = ""; }; - OBJ_32 /* NonThrowingFutureTests.swift */ = {isa = PBXFileReference; path = NonThrowingFutureTests.swift; sourceTree = ""; }; - OBJ_33 /* SafeContinuationTests.swift */ = {isa = PBXFileReference; path = SafeContinuationTests.swift; sourceTree = ""; }; - OBJ_34 /* StandardLibraryTests.swift */ = {isa = PBXFileReference; path = StandardLibraryTests.swift; sourceTree = ""; }; - OBJ_35 /* TaskOperationTests.swift */ = {isa = PBXFileReference; path = TaskOperationTests.swift; sourceTree = ""; }; - OBJ_36 /* TaskQueueTests.swift */ = {isa = PBXFileReference; path = TaskQueueTests.swift; sourceTree = ""; }; - OBJ_37 /* ThrowingFutureTests.swift */ = {isa = PBXFileReference; path = ThrowingFutureTests.swift; sourceTree = ""; }; - OBJ_38 /* XCTestCase.swift */ = {isa = PBXFileReference; path = XCTestCase.swift; sourceTree = ""; }; - OBJ_45 /* _HashTable+Bucket.swift */ = {isa = PBXFileReference; path = "_HashTable+Bucket.swift"; sourceTree = ""; }; - OBJ_46 /* _HashTable+BucketIterator.swift */ = {isa = PBXFileReference; path = "_HashTable+BucketIterator.swift"; sourceTree = ""; }; - OBJ_47 /* _HashTable+Constants.swift */ = {isa = PBXFileReference; path = "_HashTable+Constants.swift"; sourceTree = ""; }; - OBJ_48 /* _HashTable+CustomStringConvertible.swift */ = {isa = PBXFileReference; path = "_HashTable+CustomStringConvertible.swift"; sourceTree = ""; }; - OBJ_49 /* _HashTable+Testing.swift */ = {isa = PBXFileReference; path = "_HashTable+Testing.swift"; sourceTree = ""; }; - OBJ_50 /* _HashTable+UnsafeHandle.swift */ = {isa = PBXFileReference; path = "_HashTable+UnsafeHandle.swift"; sourceTree = ""; }; - OBJ_51 /* _HashTable.swift */ = {isa = PBXFileReference; path = _HashTable.swift; sourceTree = ""; }; - OBJ_52 /* _Hashtable+Header.swift */ = {isa = PBXFileReference; path = "_Hashtable+Header.swift"; sourceTree = ""; }; - OBJ_54 /* OrderedDictionary+Codable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Codable.swift"; sourceTree = ""; }; - OBJ_55 /* OrderedDictionary+CustomDebugStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+CustomDebugStringConvertible.swift"; sourceTree = ""; }; - OBJ_56 /* OrderedDictionary+CustomReflectable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+CustomReflectable.swift"; sourceTree = ""; }; - OBJ_57 /* OrderedDictionary+CustomStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+CustomStringConvertible.swift"; sourceTree = ""; }; - OBJ_58 /* OrderedDictionary+Deprecations.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Deprecations.swift"; sourceTree = ""; }; - OBJ_59 /* OrderedDictionary+Elements+SubSequence.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Elements+SubSequence.swift"; sourceTree = ""; }; + OBJ_17 /* Continuable.swift */ = {isa = PBXFileReference; path = Continuable.swift; sourceTree = ""; }; + OBJ_18 /* ContinuableCollection.swift */ = {isa = PBXFileReference; path = ContinuableCollection.swift; sourceTree = ""; }; + OBJ_19 /* GlobalContinuation.swift */ = {isa = PBXFileReference; path = GlobalContinuation.swift; sourceTree = ""; }; + OBJ_20 /* SafeContinuation.swift */ = {isa = PBXFileReference; path = SafeContinuation.swift; sourceTree = ""; }; + OBJ_21 /* SynchronizedContinuable.swift */ = {isa = PBXFileReference; path = SynchronizedContinuable.swift; sourceTree = ""; }; + OBJ_23 /* Task.swift */ = {isa = PBXFileReference; path = Task.swift; sourceTree = ""; }; + OBJ_24 /* TaskGroup.swift */ = {isa = PBXFileReference; path = TaskGroup.swift; sourceTree = ""; }; + OBJ_25 /* Future.swift */ = {isa = PBXFileReference; path = Future.swift; sourceTree = ""; }; + OBJ_27 /* Exclusible.swift */ = {isa = PBXFileReference; path = Exclusible.swift; sourceTree = ""; }; + OBJ_28 /* Locker.swift */ = {isa = PBXFileReference; path = Locker.swift; sourceTree = ""; }; + OBJ_29 /* TaskOperation.swift */ = {isa = PBXFileReference; path = TaskOperation.swift; sourceTree = ""; }; + OBJ_30 /* TaskQueue.swift */ = {isa = PBXFileReference; path = TaskQueue.swift; sourceTree = ""; }; + OBJ_31 /* TaskTracker.swift */ = {isa = PBXFileReference; path = TaskTracker.swift; sourceTree = ""; }; + OBJ_34 /* AsyncCountdownEventTests.swift */ = {isa = PBXFileReference; path = AsyncCountdownEventTests.swift; sourceTree = ""; }; + OBJ_35 /* AsyncEventTests.swift */ = {isa = PBXFileReference; path = AsyncEventTests.swift; sourceTree = ""; }; + OBJ_36 /* AsyncObjectTests.swift */ = {isa = PBXFileReference; path = AsyncObjectTests.swift; sourceTree = ""; }; + OBJ_37 /* AsyncSemaphoreTests.swift */ = {isa = PBXFileReference; path = AsyncSemaphoreTests.swift; sourceTree = ""; }; + OBJ_38 /* CancellationSourceTests.swift */ = {isa = PBXFileReference; path = CancellationSourceTests.swift; sourceTree = ""; }; + OBJ_39 /* LockerTests.swift */ = {isa = PBXFileReference; path = LockerTests.swift; sourceTree = ""; }; + OBJ_40 /* NonThrowingFutureTests.swift */ = {isa = PBXFileReference; path = NonThrowingFutureTests.swift; sourceTree = ""; }; + OBJ_41 /* SafeContinuationTests.swift */ = {isa = PBXFileReference; path = SafeContinuationTests.swift; sourceTree = ""; }; + OBJ_42 /* StandardLibraryTests.swift */ = {isa = PBXFileReference; path = StandardLibraryTests.swift; sourceTree = ""; }; + OBJ_43 /* TaskOperationTests.swift */ = {isa = PBXFileReference; path = TaskOperationTests.swift; sourceTree = ""; }; + OBJ_44 /* TaskQueueTests.swift */ = {isa = PBXFileReference; path = TaskQueueTests.swift; sourceTree = ""; }; + OBJ_45 /* ThrowingFutureTests.swift */ = {isa = PBXFileReference; path = ThrowingFutureTests.swift; sourceTree = ""; }; + OBJ_46 /* XCTestCase.swift */ = {isa = PBXFileReference; path = XCTestCase.swift; sourceTree = ""; }; + OBJ_53 /* _HashTable+Bucket.swift */ = {isa = PBXFileReference; path = "_HashTable+Bucket.swift"; sourceTree = ""; }; + OBJ_54 /* _HashTable+BucketIterator.swift */ = {isa = PBXFileReference; path = "_HashTable+BucketIterator.swift"; sourceTree = ""; }; + OBJ_55 /* _HashTable+Constants.swift */ = {isa = PBXFileReference; path = "_HashTable+Constants.swift"; sourceTree = ""; }; + OBJ_56 /* _HashTable+CustomStringConvertible.swift */ = {isa = PBXFileReference; path = "_HashTable+CustomStringConvertible.swift"; sourceTree = ""; }; + OBJ_57 /* _HashTable+Testing.swift */ = {isa = PBXFileReference; path = "_HashTable+Testing.swift"; sourceTree = ""; }; + OBJ_58 /* _HashTable+UnsafeHandle.swift */ = {isa = PBXFileReference; path = "_HashTable+UnsafeHandle.swift"; sourceTree = ""; }; + OBJ_59 /* _HashTable.swift */ = {isa = PBXFileReference; path = _HashTable.swift; sourceTree = ""; }; OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; - OBJ_60 /* OrderedDictionary+Elements.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Elements.swift"; sourceTree = ""; }; - OBJ_61 /* OrderedDictionary+Equatable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Equatable.swift"; sourceTree = ""; }; - OBJ_62 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+ExpressibleByDictionaryLiteral.swift"; sourceTree = ""; }; - OBJ_63 /* OrderedDictionary+Hashable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Hashable.swift"; sourceTree = ""; }; - OBJ_64 /* OrderedDictionary+Initializers.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Initializers.swift"; sourceTree = ""; }; - OBJ_65 /* OrderedDictionary+Invariants.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Invariants.swift"; sourceTree = ""; }; - OBJ_66 /* OrderedDictionary+Partial MutableCollection.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Partial MutableCollection.swift"; sourceTree = ""; }; - OBJ_67 /* OrderedDictionary+Partial RangeReplaceableCollection.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Partial RangeReplaceableCollection.swift"; sourceTree = ""; }; - OBJ_68 /* OrderedDictionary+Sequence.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Sequence.swift"; sourceTree = ""; }; - OBJ_69 /* OrderedDictionary+Values.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Values.swift"; sourceTree = ""; }; - OBJ_70 /* OrderedDictionary.swift */ = {isa = PBXFileReference; path = OrderedDictionary.swift; sourceTree = ""; }; - OBJ_72 /* OrderedSet+Codable.swift */ = {isa = PBXFileReference; path = "OrderedSet+Codable.swift"; sourceTree = ""; }; - OBJ_73 /* OrderedSet+CustomDebugStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedSet+CustomDebugStringConvertible.swift"; sourceTree = ""; }; - OBJ_74 /* OrderedSet+CustomReflectable.swift */ = {isa = PBXFileReference; path = "OrderedSet+CustomReflectable.swift"; sourceTree = ""; }; - OBJ_75 /* OrderedSet+CustomStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedSet+CustomStringConvertible.swift"; sourceTree = ""; }; - OBJ_76 /* OrderedSet+Diffing.swift */ = {isa = PBXFileReference; path = "OrderedSet+Diffing.swift"; sourceTree = ""; }; - OBJ_77 /* OrderedSet+Equatable.swift */ = {isa = PBXFileReference; path = "OrderedSet+Equatable.swift"; sourceTree = ""; }; - OBJ_78 /* OrderedSet+ExpressibleByArrayLiteral.swift */ = {isa = PBXFileReference; path = "OrderedSet+ExpressibleByArrayLiteral.swift"; sourceTree = ""; }; - OBJ_79 /* OrderedSet+Hashable.swift */ = {isa = PBXFileReference; path = "OrderedSet+Hashable.swift"; sourceTree = ""; }; + OBJ_60 /* _Hashtable+Header.swift */ = {isa = PBXFileReference; path = "_Hashtable+Header.swift"; sourceTree = ""; }; + OBJ_62 /* OrderedDictionary+Codable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Codable.swift"; sourceTree = ""; }; + OBJ_63 /* OrderedDictionary+CustomDebugStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+CustomDebugStringConvertible.swift"; sourceTree = ""; }; + OBJ_64 /* OrderedDictionary+CustomReflectable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+CustomReflectable.swift"; sourceTree = ""; }; + OBJ_65 /* OrderedDictionary+CustomStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+CustomStringConvertible.swift"; sourceTree = ""; }; + OBJ_66 /* OrderedDictionary+Deprecations.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Deprecations.swift"; sourceTree = ""; }; + OBJ_67 /* OrderedDictionary+Elements+SubSequence.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Elements+SubSequence.swift"; sourceTree = ""; }; + OBJ_68 /* OrderedDictionary+Elements.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Elements.swift"; sourceTree = ""; }; + OBJ_69 /* OrderedDictionary+Equatable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Equatable.swift"; sourceTree = ""; }; + OBJ_70 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+ExpressibleByDictionaryLiteral.swift"; sourceTree = ""; }; + OBJ_71 /* OrderedDictionary+Hashable.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Hashable.swift"; sourceTree = ""; }; + OBJ_72 /* OrderedDictionary+Initializers.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Initializers.swift"; sourceTree = ""; }; + OBJ_73 /* OrderedDictionary+Invariants.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Invariants.swift"; sourceTree = ""; }; + OBJ_74 /* OrderedDictionary+Partial MutableCollection.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Partial MutableCollection.swift"; sourceTree = ""; }; + OBJ_75 /* OrderedDictionary+Partial RangeReplaceableCollection.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Partial RangeReplaceableCollection.swift"; sourceTree = ""; }; + OBJ_76 /* OrderedDictionary+Sequence.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Sequence.swift"; sourceTree = ""; }; + OBJ_77 /* OrderedDictionary+Values.swift */ = {isa = PBXFileReference; path = "OrderedDictionary+Values.swift"; sourceTree = ""; }; + OBJ_78 /* OrderedDictionary.swift */ = {isa = PBXFileReference; path = OrderedDictionary.swift; sourceTree = ""; }; OBJ_8 /* AsyncObjects.xcconfig */ = {isa = PBXFileReference; name = AsyncObjects.xcconfig; path = Helpers/AsyncObjects.xcconfig; sourceTree = ""; }; - OBJ_80 /* OrderedSet+Initializers.swift */ = {isa = PBXFileReference; path = "OrderedSet+Initializers.swift"; sourceTree = ""; }; - OBJ_81 /* OrderedSet+Insertions.swift */ = {isa = PBXFileReference; path = "OrderedSet+Insertions.swift"; sourceTree = ""; }; - OBJ_82 /* OrderedSet+Invariants.swift */ = {isa = PBXFileReference; path = "OrderedSet+Invariants.swift"; sourceTree = ""; }; - OBJ_83 /* OrderedSet+Partial MutableCollection.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial MutableCollection.swift"; sourceTree = ""; }; - OBJ_84 /* OrderedSet+Partial RangeReplaceableCollection.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial RangeReplaceableCollection.swift"; sourceTree = ""; }; - OBJ_85 /* OrderedSet+Partial SetAlgebra+Basics.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial SetAlgebra+Basics.swift"; sourceTree = ""; }; - OBJ_86 /* OrderedSet+Partial SetAlgebra+Operations.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial SetAlgebra+Operations.swift"; sourceTree = ""; }; - OBJ_87 /* OrderedSet+Partial SetAlgebra+Predicates.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial SetAlgebra+Predicates.swift"; sourceTree = ""; }; - OBJ_88 /* OrderedSet+RandomAccessCollection.swift */ = {isa = PBXFileReference; path = "OrderedSet+RandomAccessCollection.swift"; sourceTree = ""; }; - OBJ_89 /* OrderedSet+ReserveCapacity.swift */ = {isa = PBXFileReference; path = "OrderedSet+ReserveCapacity.swift"; sourceTree = ""; }; - OBJ_90 /* OrderedSet+SubSequence.swift */ = {isa = PBXFileReference; path = "OrderedSet+SubSequence.swift"; sourceTree = ""; }; - OBJ_91 /* OrderedSet+Testing.swift */ = {isa = PBXFileReference; path = "OrderedSet+Testing.swift"; sourceTree = ""; }; - OBJ_92 /* OrderedSet+UnorderedView.swift */ = {isa = PBXFileReference; path = "OrderedSet+UnorderedView.swift"; sourceTree = ""; }; - OBJ_93 /* OrderedSet+UnstableInternals.swift */ = {isa = PBXFileReference; path = "OrderedSet+UnstableInternals.swift"; sourceTree = ""; }; - OBJ_94 /* OrderedSet.swift */ = {isa = PBXFileReference; path = OrderedSet.swift; sourceTree = ""; }; - OBJ_96 /* RandomAccessCollection+Offsets.swift */ = {isa = PBXFileReference; path = "RandomAccessCollection+Offsets.swift"; sourceTree = ""; }; - OBJ_97 /* _UnsafeBitset.swift */ = {isa = PBXFileReference; path = _UnsafeBitset.swift; sourceTree = ""; }; - OBJ_98 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; name = Package.swift; path = "/Users/soumyaranjanmahunt/Documents/personal_projs/AsyncObjects/.build/checkouts/swift-collections/Package.swift"; sourceTree = ""; }; + OBJ_80 /* OrderedSet+Codable.swift */ = {isa = PBXFileReference; path = "OrderedSet+Codable.swift"; sourceTree = ""; }; + OBJ_81 /* OrderedSet+CustomDebugStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedSet+CustomDebugStringConvertible.swift"; sourceTree = ""; }; + OBJ_82 /* OrderedSet+CustomReflectable.swift */ = {isa = PBXFileReference; path = "OrderedSet+CustomReflectable.swift"; sourceTree = ""; }; + OBJ_83 /* OrderedSet+CustomStringConvertible.swift */ = {isa = PBXFileReference; path = "OrderedSet+CustomStringConvertible.swift"; sourceTree = ""; }; + OBJ_84 /* OrderedSet+Diffing.swift */ = {isa = PBXFileReference; path = "OrderedSet+Diffing.swift"; sourceTree = ""; }; + OBJ_85 /* OrderedSet+Equatable.swift */ = {isa = PBXFileReference; path = "OrderedSet+Equatable.swift"; sourceTree = ""; }; + OBJ_86 /* OrderedSet+ExpressibleByArrayLiteral.swift */ = {isa = PBXFileReference; path = "OrderedSet+ExpressibleByArrayLiteral.swift"; sourceTree = ""; }; + OBJ_87 /* OrderedSet+Hashable.swift */ = {isa = PBXFileReference; path = "OrderedSet+Hashable.swift"; sourceTree = ""; }; + OBJ_88 /* OrderedSet+Initializers.swift */ = {isa = PBXFileReference; path = "OrderedSet+Initializers.swift"; sourceTree = ""; }; + OBJ_89 /* OrderedSet+Insertions.swift */ = {isa = PBXFileReference; path = "OrderedSet+Insertions.swift"; sourceTree = ""; }; + OBJ_90 /* OrderedSet+Invariants.swift */ = {isa = PBXFileReference; path = "OrderedSet+Invariants.swift"; sourceTree = ""; }; + OBJ_91 /* OrderedSet+Partial MutableCollection.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial MutableCollection.swift"; sourceTree = ""; }; + OBJ_92 /* OrderedSet+Partial RangeReplaceableCollection.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial RangeReplaceableCollection.swift"; sourceTree = ""; }; + OBJ_93 /* OrderedSet+Partial SetAlgebra+Basics.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial SetAlgebra+Basics.swift"; sourceTree = ""; }; + OBJ_94 /* OrderedSet+Partial SetAlgebra+Operations.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial SetAlgebra+Operations.swift"; sourceTree = ""; }; + OBJ_95 /* OrderedSet+Partial SetAlgebra+Predicates.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial SetAlgebra+Predicates.swift"; sourceTree = ""; }; + OBJ_96 /* OrderedSet+RandomAccessCollection.swift */ = {isa = PBXFileReference; path = "OrderedSet+RandomAccessCollection.swift"; sourceTree = ""; }; + OBJ_97 /* OrderedSet+ReserveCapacity.swift */ = {isa = PBXFileReference; path = "OrderedSet+ReserveCapacity.swift"; sourceTree = ""; }; + OBJ_98 /* OrderedSet+SubSequence.swift */ = {isa = PBXFileReference; path = "OrderedSet+SubSequence.swift"; sourceTree = ""; }; + OBJ_99 /* OrderedSet+Testing.swift */ = {isa = PBXFileReference; path = "OrderedSet+Testing.swift"; sourceTree = ""; }; asyncobjects::AsyncObjects::Product /* AsyncObjects.framework */ = {isa = PBXFileReference; path = AsyncObjects.framework; sourceTree = BUILT_PRODUCTS_DIR; }; asyncobjects::AsyncObjectsTests::Product /* AsyncObjectsTests.xctest */ = {isa = PBXFileReference; path = AsyncObjectsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; swift-collections::OrderedCollections::Product /* OrderedCollections.framework */ = {isa = PBXFileReference; path = OrderedCollections.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - OBJ_121 /* Frameworks */ = { + OBJ_134 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; files = ( - OBJ_122 /* OrderedCollections.framework in Frameworks */, + OBJ_135 /* OrderedCollections.framework in Frameworks */, ); }; - OBJ_154 /* Frameworks */ = { + OBJ_167 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; files = ( - OBJ_155 /* AsyncObjects.framework in Frameworks */, - OBJ_156 /* OrderedCollections.framework in Frameworks */, + OBJ_168 /* AsyncObjects.framework in Frameworks */, + OBJ_169 /* OrderedCollections.framework in Frameworks */, ); }; - OBJ_213 /* Frameworks */ = { + OBJ_226 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; files = ( ); @@ -221,72 +231,125 @@ OBJ_13 /* AsyncObject.swift */, OBJ_14 /* AsyncSemaphore.swift */, OBJ_15 /* CancellationSource.swift */, - OBJ_16 /* Continuable.swift */, - OBJ_17 /* Future.swift */, - OBJ_18 /* Locker.swift */, - OBJ_19 /* SafeContinuation.swift */, - OBJ_20 /* Task.swift */, - OBJ_21 /* TaskOperation.swift */, - OBJ_22 /* TaskQueue.swift */, - OBJ_23 /* TaskTracker.swift */, - B7A072784A06B1A1C700CC40 /* AsyncObjects.docc */, + OBJ_16 /* Continuation */, + OBJ_22 /* Extensions */, + OBJ_25 /* Future.swift */, + OBJ_26 /* Locks */, + OBJ_29 /* TaskOperation.swift */, + OBJ_30 /* TaskQueue.swift */, + OBJ_31 /* TaskTracker.swift */, + EB6E2F9D4FEF6DDD65D0B251 /* AsyncObjects.docc */, ); name = AsyncObjects; path = Sources/AsyncObjects; sourceTree = SOURCE_ROOT; }; - OBJ_24 /* Tests */ = { + OBJ_103 /* Utilities */ = { isa = PBXGroup; children = ( - OBJ_25 /* AsyncObjectsTests */, + OBJ_104 /* RandomAccessCollection+Offsets.swift */, + OBJ_105 /* _UnsafeBitset.swift */, + ); + name = Utilities; + path = Utilities; + sourceTree = ""; + }; + OBJ_107 /* Products */ = { + isa = PBXGroup; + children = ( + asyncobjects::AsyncObjects::Product /* AsyncObjects.framework */, + asyncobjects::AsyncObjectsTests::Product /* AsyncObjectsTests.xctest */, + swift-collections::OrderedCollections::Product /* OrderedCollections.framework */, + ); + name = Products; + path = ""; + sourceTree = BUILT_PRODUCTS_DIR; + }; + OBJ_16 /* Continuation */ = { + isa = PBXGroup; + children = ( + OBJ_17 /* Continuable.swift */, + OBJ_18 /* ContinuableCollection.swift */, + OBJ_19 /* GlobalContinuation.swift */, + OBJ_20 /* SafeContinuation.swift */, + OBJ_21 /* SynchronizedContinuable.swift */, + ); + name = Continuation; + path = Continuation; + sourceTree = ""; + }; + OBJ_22 /* Extensions */ = { + isa = PBXGroup; + children = ( + OBJ_23 /* Task.swift */, + OBJ_24 /* TaskGroup.swift */, + ); + name = Extensions; + path = Extensions; + sourceTree = ""; + }; + OBJ_26 /* Locks */ = { + isa = PBXGroup; + children = ( + OBJ_27 /* Exclusible.swift */, + OBJ_28 /* Locker.swift */, + ); + name = Locks; + path = Locks; + sourceTree = ""; + }; + OBJ_32 /* Tests */ = { + isa = PBXGroup; + children = ( + OBJ_33 /* AsyncObjectsTests */, ); name = Tests; path = ""; sourceTree = SOURCE_ROOT; }; - OBJ_25 /* AsyncObjectsTests */ = { + OBJ_33 /* AsyncObjectsTests */ = { isa = PBXGroup; children = ( - OBJ_26 /* AsyncCountdownEventTests.swift */, - OBJ_27 /* AsyncEventTests.swift */, - OBJ_28 /* AsyncObjectTests.swift */, - OBJ_29 /* AsyncSemaphoreTests.swift */, - OBJ_30 /* CancellationSourceTests.swift */, - OBJ_31 /* LockerTests.swift */, - OBJ_32 /* NonThrowingFutureTests.swift */, - OBJ_33 /* SafeContinuationTests.swift */, - OBJ_34 /* StandardLibraryTests.swift */, - OBJ_35 /* TaskOperationTests.swift */, - OBJ_36 /* TaskQueueTests.swift */, - OBJ_37 /* ThrowingFutureTests.swift */, - OBJ_38 /* XCTestCase.swift */, + OBJ_34 /* AsyncCountdownEventTests.swift */, + OBJ_35 /* AsyncEventTests.swift */, + OBJ_36 /* AsyncObjectTests.swift */, + OBJ_37 /* AsyncSemaphoreTests.swift */, + OBJ_38 /* CancellationSourceTests.swift */, + OBJ_39 /* LockerTests.swift */, + OBJ_40 /* NonThrowingFutureTests.swift */, + OBJ_41 /* SafeContinuationTests.swift */, + OBJ_42 /* StandardLibraryTests.swift */, + OBJ_43 /* TaskOperationTests.swift */, + OBJ_44 /* TaskQueueTests.swift */, + OBJ_45 /* ThrowingFutureTests.swift */, + OBJ_46 /* XCTestCase.swift */, ); name = AsyncObjectsTests; path = Tests/AsyncObjectsTests; sourceTree = SOURCE_ROOT; }; - OBJ_39 /* Dependencies */ = { + OBJ_47 /* Dependencies */ = { isa = PBXGroup; children = ( - OBJ_40 /* swift-collections 1.0.2 */, + OBJ_48 /* swift-collections 1.0.2 */, ); name = Dependencies; path = ""; sourceTree = ""; }; - OBJ_40 /* swift-collections 1.0.2 */ = { + OBJ_48 /* swift-collections 1.0.2 */ = { isa = PBXGroup; children = ( - OBJ_41 /* Collections */, - OBJ_42 /* DequeModule */, - OBJ_43 /* OrderedCollections */, - OBJ_98 /* Package.swift */, + OBJ_49 /* Collections */, + OBJ_50 /* DequeModule */, + OBJ_51 /* OrderedCollections */, + OBJ_106 /* Package.swift */, ); name = "swift-collections 1.0.2"; path = ""; sourceTree = SOURCE_ROOT; }; - OBJ_41 /* Collections */ = { + OBJ_49 /* Collections */ = { isa = PBXGroup; children = ( ); @@ -294,7 +357,20 @@ path = ".build/checkouts/swift-collections/Sources/Collections"; sourceTree = SOURCE_ROOT; }; - OBJ_42 /* DequeModule */ = { + OBJ_5 = { + isa = PBXGroup; + children = ( + OBJ_6 /* Package.swift */, + OBJ_7 /* Configs */, + OBJ_9 /* Sources */, + OBJ_32 /* Tests */, + OBJ_47 /* Dependencies */, + OBJ_107 /* Products */, + ); + path = ""; + sourceTree = ""; + }; + OBJ_50 /* DequeModule */ = { isa = PBXGroup; children = ( ); @@ -302,67 +378,54 @@ path = ".build/checkouts/swift-collections/Sources/DequeModule"; sourceTree = SOURCE_ROOT; }; - OBJ_43 /* OrderedCollections */ = { + OBJ_51 /* OrderedCollections */ = { isa = PBXGroup; children = ( - OBJ_44 /* HashTable */, - OBJ_53 /* OrderedDictionary */, - OBJ_71 /* OrderedSet */, - OBJ_95 /* Utilities */, + OBJ_52 /* HashTable */, + OBJ_61 /* OrderedDictionary */, + OBJ_79 /* OrderedSet */, + OBJ_103 /* Utilities */, ); name = OrderedCollections; path = ".build/checkouts/swift-collections/Sources/OrderedCollections"; sourceTree = SOURCE_ROOT; }; - OBJ_44 /* HashTable */ = { + OBJ_52 /* HashTable */ = { isa = PBXGroup; children = ( - OBJ_45 /* _HashTable+Bucket.swift */, - OBJ_46 /* _HashTable+BucketIterator.swift */, - OBJ_47 /* _HashTable+Constants.swift */, - OBJ_48 /* _HashTable+CustomStringConvertible.swift */, - OBJ_49 /* _HashTable+Testing.swift */, - OBJ_50 /* _HashTable+UnsafeHandle.swift */, - OBJ_51 /* _HashTable.swift */, - OBJ_52 /* _Hashtable+Header.swift */, + OBJ_53 /* _HashTable+Bucket.swift */, + OBJ_54 /* _HashTable+BucketIterator.swift */, + OBJ_55 /* _HashTable+Constants.swift */, + OBJ_56 /* _HashTable+CustomStringConvertible.swift */, + OBJ_57 /* _HashTable+Testing.swift */, + OBJ_58 /* _HashTable+UnsafeHandle.swift */, + OBJ_59 /* _HashTable.swift */, + OBJ_60 /* _Hashtable+Header.swift */, ); name = HashTable; path = HashTable; sourceTree = ""; }; - OBJ_5 = { - isa = PBXGroup; - children = ( - OBJ_6 /* Package.swift */, - OBJ_7 /* Configs */, - OBJ_9 /* Sources */, - OBJ_24 /* Tests */, - OBJ_39 /* Dependencies */, - OBJ_99 /* Products */, - ); - path = ""; - sourceTree = ""; - }; - OBJ_53 /* OrderedDictionary */ = { + OBJ_61 /* OrderedDictionary */ = { isa = PBXGroup; children = ( - OBJ_54 /* OrderedDictionary+Codable.swift */, - OBJ_55 /* OrderedDictionary+CustomDebugStringConvertible.swift */, - OBJ_56 /* OrderedDictionary+CustomReflectable.swift */, - OBJ_57 /* OrderedDictionary+CustomStringConvertible.swift */, - OBJ_58 /* OrderedDictionary+Deprecations.swift */, - OBJ_59 /* OrderedDictionary+Elements+SubSequence.swift */, - OBJ_60 /* OrderedDictionary+Elements.swift */, - OBJ_61 /* OrderedDictionary+Equatable.swift */, - OBJ_62 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift */, - OBJ_63 /* OrderedDictionary+Hashable.swift */, - OBJ_64 /* OrderedDictionary+Initializers.swift */, - OBJ_65 /* OrderedDictionary+Invariants.swift */, - OBJ_66 /* OrderedDictionary+Partial MutableCollection.swift */, - OBJ_67 /* OrderedDictionary+Partial RangeReplaceableCollection.swift */, - OBJ_68 /* OrderedDictionary+Sequence.swift */, - OBJ_69 /* OrderedDictionary+Values.swift */, - OBJ_70 /* OrderedDictionary.swift */, + OBJ_62 /* OrderedDictionary+Codable.swift */, + OBJ_63 /* OrderedDictionary+CustomDebugStringConvertible.swift */, + OBJ_64 /* OrderedDictionary+CustomReflectable.swift */, + OBJ_65 /* OrderedDictionary+CustomStringConvertible.swift */, + OBJ_66 /* OrderedDictionary+Deprecations.swift */, + OBJ_67 /* OrderedDictionary+Elements+SubSequence.swift */, + OBJ_68 /* OrderedDictionary+Elements.swift */, + OBJ_69 /* OrderedDictionary+Equatable.swift */, + OBJ_70 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift */, + OBJ_71 /* OrderedDictionary+Hashable.swift */, + OBJ_72 /* OrderedDictionary+Initializers.swift */, + OBJ_73 /* OrderedDictionary+Invariants.swift */, + OBJ_74 /* OrderedDictionary+Partial MutableCollection.swift */, + OBJ_75 /* OrderedDictionary+Partial RangeReplaceableCollection.swift */, + OBJ_76 /* OrderedDictionary+Sequence.swift */, + OBJ_77 /* OrderedDictionary+Values.swift */, + OBJ_78 /* OrderedDictionary.swift */, ); name = OrderedDictionary; path = OrderedDictionary; @@ -377,32 +440,32 @@ path = ""; sourceTree = ""; }; - OBJ_71 /* OrderedSet */ = { + OBJ_79 /* OrderedSet */ = { isa = PBXGroup; children = ( - OBJ_72 /* OrderedSet+Codable.swift */, - OBJ_73 /* OrderedSet+CustomDebugStringConvertible.swift */, - OBJ_74 /* OrderedSet+CustomReflectable.swift */, - OBJ_75 /* OrderedSet+CustomStringConvertible.swift */, - OBJ_76 /* OrderedSet+Diffing.swift */, - OBJ_77 /* OrderedSet+Equatable.swift */, - OBJ_78 /* OrderedSet+ExpressibleByArrayLiteral.swift */, - OBJ_79 /* OrderedSet+Hashable.swift */, - OBJ_80 /* OrderedSet+Initializers.swift */, - OBJ_81 /* OrderedSet+Insertions.swift */, - OBJ_82 /* OrderedSet+Invariants.swift */, - OBJ_83 /* OrderedSet+Partial MutableCollection.swift */, - OBJ_84 /* OrderedSet+Partial RangeReplaceableCollection.swift */, - OBJ_85 /* OrderedSet+Partial SetAlgebra+Basics.swift */, - OBJ_86 /* OrderedSet+Partial SetAlgebra+Operations.swift */, - OBJ_87 /* OrderedSet+Partial SetAlgebra+Predicates.swift */, - OBJ_88 /* OrderedSet+RandomAccessCollection.swift */, - OBJ_89 /* OrderedSet+ReserveCapacity.swift */, - OBJ_90 /* OrderedSet+SubSequence.swift */, - OBJ_91 /* OrderedSet+Testing.swift */, - OBJ_92 /* OrderedSet+UnorderedView.swift */, - OBJ_93 /* OrderedSet+UnstableInternals.swift */, - OBJ_94 /* OrderedSet.swift */, + OBJ_80 /* OrderedSet+Codable.swift */, + OBJ_81 /* OrderedSet+CustomDebugStringConvertible.swift */, + OBJ_82 /* OrderedSet+CustomReflectable.swift */, + OBJ_83 /* OrderedSet+CustomStringConvertible.swift */, + OBJ_84 /* OrderedSet+Diffing.swift */, + OBJ_85 /* OrderedSet+Equatable.swift */, + OBJ_86 /* OrderedSet+ExpressibleByArrayLiteral.swift */, + OBJ_87 /* OrderedSet+Hashable.swift */, + OBJ_88 /* OrderedSet+Initializers.swift */, + OBJ_89 /* OrderedSet+Insertions.swift */, + OBJ_90 /* OrderedSet+Invariants.swift */, + OBJ_91 /* OrderedSet+Partial MutableCollection.swift */, + OBJ_92 /* OrderedSet+Partial RangeReplaceableCollection.swift */, + OBJ_93 /* OrderedSet+Partial SetAlgebra+Basics.swift */, + OBJ_94 /* OrderedSet+Partial SetAlgebra+Operations.swift */, + OBJ_95 /* OrderedSet+Partial SetAlgebra+Predicates.swift */, + OBJ_96 /* OrderedSet+RandomAccessCollection.swift */, + OBJ_97 /* OrderedSet+ReserveCapacity.swift */, + OBJ_98 /* OrderedSet+SubSequence.swift */, + OBJ_99 /* OrderedSet+Testing.swift */, + OBJ_100 /* OrderedSet+UnorderedView.swift */, + OBJ_101 /* OrderedSet+UnstableInternals.swift */, + OBJ_102 /* OrderedSet.swift */, ); name = OrderedSet; path = OrderedSet; @@ -417,41 +480,20 @@ path = ""; sourceTree = SOURCE_ROOT; }; - OBJ_95 /* Utilities */ = { - isa = PBXGroup; - children = ( - OBJ_96 /* RandomAccessCollection+Offsets.swift */, - OBJ_97 /* _UnsafeBitset.swift */, - ); - name = Utilities; - path = Utilities; - sourceTree = ""; - }; - OBJ_99 /* Products */ = { - isa = PBXGroup; - children = ( - asyncobjects::AsyncObjects::Product /* AsyncObjects.framework */, - asyncobjects::AsyncObjectsTests::Product /* AsyncObjectsTests.xctest */, - swift-collections::OrderedCollections::Product /* OrderedCollections.framework */, - ); - name = Products; - path = ""; - sourceTree = BUILT_PRODUCTS_DIR; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ asyncobjects::AsyncObjects /* AsyncObjects */ = { isa = PBXNativeTarget; - buildConfigurationList = OBJ_104 /* Build configuration list for PBXNativeTarget "AsyncObjects" */; + buildConfigurationList = OBJ_112 /* Build configuration list for PBXNativeTarget "AsyncObjects" */; buildPhases = ( - OBJ_107 /* Sources */, - OBJ_121 /* Frameworks */, + OBJ_115 /* Sources */, + OBJ_134 /* Frameworks */, ); buildRules = ( ); dependencies = ( - OBJ_123 /* PBXTargetDependency */, + OBJ_136 /* PBXTargetDependency */, ); name = AsyncObjects; productName = AsyncObjects; @@ -460,16 +502,16 @@ }; asyncobjects::AsyncObjectsTests /* AsyncObjectsTests */ = { isa = PBXNativeTarget; - buildConfigurationList = OBJ_137 /* Build configuration list for PBXNativeTarget "AsyncObjectsTests" */; + buildConfigurationList = OBJ_150 /* Build configuration list for PBXNativeTarget "AsyncObjectsTests" */; buildPhases = ( - OBJ_140 /* Sources */, - OBJ_154 /* Frameworks */, + OBJ_153 /* Sources */, + OBJ_167 /* Frameworks */, ); buildRules = ( ); dependencies = ( - OBJ_157 /* PBXTargetDependency */, - OBJ_158 /* PBXTargetDependency */, + OBJ_170 /* PBXTargetDependency */, + OBJ_171 /* PBXTargetDependency */, ); name = AsyncObjectsTests; productName = AsyncObjectsTests; @@ -478,9 +520,9 @@ }; asyncobjects::SwiftPMPackageDescription /* AsyncObjectsPackageDescription */ = { isa = PBXNativeTarget; - buildConfigurationList = OBJ_126 /* Build configuration list for PBXNativeTarget "AsyncObjectsPackageDescription" */; + buildConfigurationList = OBJ_139 /* Build configuration list for PBXNativeTarget "AsyncObjectsPackageDescription" */; buildPhases = ( - OBJ_129 /* Sources */, + OBJ_142 /* Sources */, ); buildRules = ( ); @@ -492,10 +534,10 @@ }; swift-collections::OrderedCollections /* OrderedCollections */ = { isa = PBXNativeTarget; - buildConfigurationList = OBJ_159 /* Build configuration list for PBXNativeTarget "OrderedCollections" */; + buildConfigurationList = OBJ_172 /* Build configuration list for PBXNativeTarget "OrderedCollections" */; buildPhases = ( - OBJ_162 /* Sources */, - OBJ_213 /* Frameworks */, + OBJ_175 /* Sources */, + OBJ_226 /* Frameworks */, ); buildRules = ( ); @@ -508,9 +550,9 @@ }; swift-collections::SwiftPMPackageDescription /* swift-collectionsPackageDescription */ = { isa = PBXNativeTarget; - buildConfigurationList = OBJ_215 /* Build configuration list for PBXNativeTarget "swift-collectionsPackageDescription" */; + buildConfigurationList = OBJ_228 /* Build configuration list for PBXNativeTarget "swift-collectionsPackageDescription" */; buildPhases = ( - OBJ_218 /* Sources */, + OBJ_231 /* Sources */, ); buildRules = ( ); @@ -537,7 +579,7 @@ en, ); mainGroup = OBJ_5; - productRefGroup = OBJ_99 /* Products */; + productRefGroup = OBJ_107 /* Products */; projectDirPath = .; targets = ( asyncobjects::AsyncObjects /* AsyncObjects */, @@ -551,133 +593,138 @@ /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ - OBJ_107 /* Sources */ = { + OBJ_115 /* Sources */ = { isa = PBXSourcesBuildPhase; files = ( - OBJ_108 /* AsyncCountdownEvent.swift in Sources */, - OBJ_109 /* AsyncEvent.swift in Sources */, - OBJ_110 /* AsyncObject.swift in Sources */, - OBJ_111 /* AsyncSemaphore.swift in Sources */, - OBJ_112 /* CancellationSource.swift in Sources */, - OBJ_113 /* Continuable.swift in Sources */, - OBJ_114 /* Future.swift in Sources */, - OBJ_115 /* Locker.swift in Sources */, - OBJ_116 /* SafeContinuation.swift in Sources */, - OBJ_117 /* Task.swift in Sources */, - OBJ_118 /* TaskOperation.swift in Sources */, - OBJ_119 /* TaskQueue.swift in Sources */, - OBJ_120 /* TaskTracker.swift in Sources */, - 0663FA4A7B71E49448BE9547 /* AsyncObjects.docc in Sources */, + OBJ_116 /* AsyncCountdownEvent.swift in Sources */, + OBJ_117 /* AsyncEvent.swift in Sources */, + OBJ_118 /* AsyncObject.swift in Sources */, + OBJ_119 /* AsyncSemaphore.swift in Sources */, + OBJ_120 /* CancellationSource.swift in Sources */, + OBJ_121 /* Continuable.swift in Sources */, + OBJ_122 /* ContinuableCollection.swift in Sources */, + OBJ_123 /* GlobalContinuation.swift in Sources */, + OBJ_124 /* SafeContinuation.swift in Sources */, + OBJ_125 /* SynchronizedContinuable.swift in Sources */, + OBJ_126 /* Task.swift in Sources */, + OBJ_127 /* TaskGroup.swift in Sources */, + OBJ_128 /* Future.swift in Sources */, + OBJ_129 /* Exclusible.swift in Sources */, + OBJ_130 /* Locker.swift in Sources */, + OBJ_131 /* TaskOperation.swift in Sources */, + OBJ_132 /* TaskQueue.swift in Sources */, + OBJ_133 /* TaskTracker.swift in Sources */, + DFEF4D3A44700284C230A854 /* AsyncObjects.docc in Sources */, ); }; - OBJ_129 /* Sources */ = { + OBJ_142 /* Sources */ = { isa = PBXSourcesBuildPhase; files = ( - OBJ_130 /* Package.swift in Sources */, + OBJ_143 /* Package.swift in Sources */, ); }; - OBJ_140 /* Sources */ = { + OBJ_153 /* Sources */ = { isa = PBXSourcesBuildPhase; files = ( - OBJ_141 /* AsyncCountdownEventTests.swift in Sources */, - OBJ_142 /* AsyncEventTests.swift in Sources */, - OBJ_143 /* AsyncObjectTests.swift in Sources */, - OBJ_144 /* AsyncSemaphoreTests.swift in Sources */, - OBJ_145 /* CancellationSourceTests.swift in Sources */, - OBJ_146 /* LockerTests.swift in Sources */, - OBJ_147 /* NonThrowingFutureTests.swift in Sources */, - OBJ_148 /* SafeContinuationTests.swift in Sources */, - OBJ_149 /* StandardLibraryTests.swift in Sources */, - OBJ_150 /* TaskOperationTests.swift in Sources */, - OBJ_151 /* TaskQueueTests.swift in Sources */, - OBJ_152 /* ThrowingFutureTests.swift in Sources */, - OBJ_153 /* XCTestCase.swift in Sources */, + OBJ_154 /* AsyncCountdownEventTests.swift in Sources */, + OBJ_155 /* AsyncEventTests.swift in Sources */, + OBJ_156 /* AsyncObjectTests.swift in Sources */, + OBJ_157 /* AsyncSemaphoreTests.swift in Sources */, + OBJ_158 /* CancellationSourceTests.swift in Sources */, + OBJ_159 /* LockerTests.swift in Sources */, + OBJ_160 /* NonThrowingFutureTests.swift in Sources */, + OBJ_161 /* SafeContinuationTests.swift in Sources */, + OBJ_162 /* StandardLibraryTests.swift in Sources */, + OBJ_163 /* TaskOperationTests.swift in Sources */, + OBJ_164 /* TaskQueueTests.swift in Sources */, + OBJ_165 /* ThrowingFutureTests.swift in Sources */, + OBJ_166 /* XCTestCase.swift in Sources */, ); }; - OBJ_162 /* Sources */ = { + OBJ_175 /* Sources */ = { isa = PBXSourcesBuildPhase; files = ( - OBJ_163 /* _HashTable+Bucket.swift in Sources */, - OBJ_164 /* _HashTable+BucketIterator.swift in Sources */, - OBJ_165 /* _HashTable+Constants.swift in Sources */, - OBJ_166 /* _HashTable+CustomStringConvertible.swift in Sources */, - OBJ_167 /* _HashTable+Testing.swift in Sources */, - OBJ_168 /* _HashTable+UnsafeHandle.swift in Sources */, - OBJ_169 /* _HashTable.swift in Sources */, - OBJ_170 /* _Hashtable+Header.swift in Sources */, - OBJ_171 /* OrderedDictionary+Codable.swift in Sources */, - OBJ_172 /* OrderedDictionary+CustomDebugStringConvertible.swift in Sources */, - OBJ_173 /* OrderedDictionary+CustomReflectable.swift in Sources */, - OBJ_174 /* OrderedDictionary+CustomStringConvertible.swift in Sources */, - OBJ_175 /* OrderedDictionary+Deprecations.swift in Sources */, - OBJ_176 /* OrderedDictionary+Elements+SubSequence.swift in Sources */, - OBJ_177 /* OrderedDictionary+Elements.swift in Sources */, - OBJ_178 /* OrderedDictionary+Equatable.swift in Sources */, - OBJ_179 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift in Sources */, - OBJ_180 /* OrderedDictionary+Hashable.swift in Sources */, - OBJ_181 /* OrderedDictionary+Initializers.swift in Sources */, - OBJ_182 /* OrderedDictionary+Invariants.swift in Sources */, - OBJ_183 /* OrderedDictionary+Partial MutableCollection.swift in Sources */, - OBJ_184 /* OrderedDictionary+Partial RangeReplaceableCollection.swift in Sources */, - OBJ_185 /* OrderedDictionary+Sequence.swift in Sources */, - OBJ_186 /* OrderedDictionary+Values.swift in Sources */, - OBJ_187 /* OrderedDictionary.swift in Sources */, - OBJ_188 /* OrderedSet+Codable.swift in Sources */, - OBJ_189 /* OrderedSet+CustomDebugStringConvertible.swift in Sources */, - OBJ_190 /* OrderedSet+CustomReflectable.swift in Sources */, - OBJ_191 /* OrderedSet+CustomStringConvertible.swift in Sources */, - OBJ_192 /* OrderedSet+Diffing.swift in Sources */, - OBJ_193 /* OrderedSet+Equatable.swift in Sources */, - OBJ_194 /* OrderedSet+ExpressibleByArrayLiteral.swift in Sources */, - OBJ_195 /* OrderedSet+Hashable.swift in Sources */, - OBJ_196 /* OrderedSet+Initializers.swift in Sources */, - OBJ_197 /* OrderedSet+Insertions.swift in Sources */, - OBJ_198 /* OrderedSet+Invariants.swift in Sources */, - OBJ_199 /* OrderedSet+Partial MutableCollection.swift in Sources */, - OBJ_200 /* OrderedSet+Partial RangeReplaceableCollection.swift in Sources */, - OBJ_201 /* OrderedSet+Partial SetAlgebra+Basics.swift in Sources */, - OBJ_202 /* OrderedSet+Partial SetAlgebra+Operations.swift in Sources */, - OBJ_203 /* OrderedSet+Partial SetAlgebra+Predicates.swift in Sources */, - OBJ_204 /* OrderedSet+RandomAccessCollection.swift in Sources */, - OBJ_205 /* OrderedSet+ReserveCapacity.swift in Sources */, - OBJ_206 /* OrderedSet+SubSequence.swift in Sources */, - OBJ_207 /* OrderedSet+Testing.swift in Sources */, - OBJ_208 /* OrderedSet+UnorderedView.swift in Sources */, - OBJ_209 /* OrderedSet+UnstableInternals.swift in Sources */, - OBJ_210 /* OrderedSet.swift in Sources */, - OBJ_211 /* RandomAccessCollection+Offsets.swift in Sources */, - OBJ_212 /* _UnsafeBitset.swift in Sources */, + OBJ_176 /* _HashTable+Bucket.swift in Sources */, + OBJ_177 /* _HashTable+BucketIterator.swift in Sources */, + OBJ_178 /* _HashTable+Constants.swift in Sources */, + OBJ_179 /* _HashTable+CustomStringConvertible.swift in Sources */, + OBJ_180 /* _HashTable+Testing.swift in Sources */, + OBJ_181 /* _HashTable+UnsafeHandle.swift in Sources */, + OBJ_182 /* _HashTable.swift in Sources */, + OBJ_183 /* _Hashtable+Header.swift in Sources */, + OBJ_184 /* OrderedDictionary+Codable.swift in Sources */, + OBJ_185 /* OrderedDictionary+CustomDebugStringConvertible.swift in Sources */, + OBJ_186 /* OrderedDictionary+CustomReflectable.swift in Sources */, + OBJ_187 /* OrderedDictionary+CustomStringConvertible.swift in Sources */, + OBJ_188 /* OrderedDictionary+Deprecations.swift in Sources */, + OBJ_189 /* OrderedDictionary+Elements+SubSequence.swift in Sources */, + OBJ_190 /* OrderedDictionary+Elements.swift in Sources */, + OBJ_191 /* OrderedDictionary+Equatable.swift in Sources */, + OBJ_192 /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift in Sources */, + OBJ_193 /* OrderedDictionary+Hashable.swift in Sources */, + OBJ_194 /* OrderedDictionary+Initializers.swift in Sources */, + OBJ_195 /* OrderedDictionary+Invariants.swift in Sources */, + OBJ_196 /* OrderedDictionary+Partial MutableCollection.swift in Sources */, + OBJ_197 /* OrderedDictionary+Partial RangeReplaceableCollection.swift in Sources */, + OBJ_198 /* OrderedDictionary+Sequence.swift in Sources */, + OBJ_199 /* OrderedDictionary+Values.swift in Sources */, + OBJ_200 /* OrderedDictionary.swift in Sources */, + OBJ_201 /* OrderedSet+Codable.swift in Sources */, + OBJ_202 /* OrderedSet+CustomDebugStringConvertible.swift in Sources */, + OBJ_203 /* OrderedSet+CustomReflectable.swift in Sources */, + OBJ_204 /* OrderedSet+CustomStringConvertible.swift in Sources */, + OBJ_205 /* OrderedSet+Diffing.swift in Sources */, + OBJ_206 /* OrderedSet+Equatable.swift in Sources */, + OBJ_207 /* OrderedSet+ExpressibleByArrayLiteral.swift in Sources */, + OBJ_208 /* OrderedSet+Hashable.swift in Sources */, + OBJ_209 /* OrderedSet+Initializers.swift in Sources */, + OBJ_210 /* OrderedSet+Insertions.swift in Sources */, + OBJ_211 /* OrderedSet+Invariants.swift in Sources */, + OBJ_212 /* OrderedSet+Partial MutableCollection.swift in Sources */, + OBJ_213 /* OrderedSet+Partial RangeReplaceableCollection.swift in Sources */, + OBJ_214 /* OrderedSet+Partial SetAlgebra+Basics.swift in Sources */, + OBJ_215 /* OrderedSet+Partial SetAlgebra+Operations.swift in Sources */, + OBJ_216 /* OrderedSet+Partial SetAlgebra+Predicates.swift in Sources */, + OBJ_217 /* OrderedSet+RandomAccessCollection.swift in Sources */, + OBJ_218 /* OrderedSet+ReserveCapacity.swift in Sources */, + OBJ_219 /* OrderedSet+SubSequence.swift in Sources */, + OBJ_220 /* OrderedSet+Testing.swift in Sources */, + OBJ_221 /* OrderedSet+UnorderedView.swift in Sources */, + OBJ_222 /* OrderedSet+UnstableInternals.swift in Sources */, + OBJ_223 /* OrderedSet.swift in Sources */, + OBJ_224 /* RandomAccessCollection+Offsets.swift in Sources */, + OBJ_225 /* _UnsafeBitset.swift in Sources */, ); }; - OBJ_218 /* Sources */ = { + OBJ_231 /* Sources */ = { isa = PBXSourcesBuildPhase; files = ( - OBJ_219 /* Package.swift in Sources */, + OBJ_232 /* Package.swift in Sources */, ); }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - OBJ_123 /* PBXTargetDependency */ = { + OBJ_136 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = swift-collections::OrderedCollections /* OrderedCollections */; }; - OBJ_135 /* PBXTargetDependency */ = { + OBJ_148 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = asyncobjects::AsyncObjectsTests /* AsyncObjectsTests */; }; - OBJ_157 /* PBXTargetDependency */ = { + OBJ_170 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = asyncobjects::AsyncObjects /* AsyncObjects */; }; - OBJ_158 /* PBXTargetDependency */ = { + OBJ_171 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = swift-collections::OrderedCollections /* OrderedCollections */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - OBJ_105 /* Debug */ = { + OBJ_113 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = OBJ_8 /* AsyncObjects.xcconfig */; buildSettings = { @@ -695,7 +742,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.15; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; - OTHER_SWIFT_FLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited) -Xfrontend -warn-concurrency -enable-actor-data-race-checks -require-explicit-sendable"; PRODUCT_BUNDLE_IDENTIFIER = AsyncObjects; PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; @@ -708,7 +755,7 @@ }; name = Debug; }; - OBJ_106 /* Release */ = { + OBJ_114 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = OBJ_8 /* AsyncObjects.xcconfig */; buildSettings = { @@ -726,7 +773,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.15; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; - OTHER_SWIFT_FLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited) -Xfrontend -warn-concurrency -enable-actor-data-race-checks -require-explicit-sendable"; PRODUCT_BUNDLE_IDENTIFIER = AsyncObjects; PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; @@ -739,7 +786,7 @@ }; name = Release; }; - OBJ_127 /* Debug */ = { + OBJ_140 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { LD = /usr/bin/true; @@ -748,7 +795,7 @@ }; name = Debug; }; - OBJ_128 /* Release */ = { + OBJ_141 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { LD = /usr/bin/true; @@ -757,19 +804,19 @@ }; name = Release; }; - OBJ_133 /* Debug */ = { + OBJ_146 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { }; name = Debug; }; - OBJ_134 /* Release */ = { + OBJ_147 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { }; name = Release; }; - OBJ_138 /* Debug */ = { + OBJ_151 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = OBJ_8 /* AsyncObjects.xcconfig */; buildSettings = { @@ -797,7 +844,7 @@ }; name = Debug; }; - OBJ_139 /* Release */ = { + OBJ_152 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = OBJ_8 /* AsyncObjects.xcconfig */; buildSettings = { @@ -825,7 +872,7 @@ }; name = Release; }; - OBJ_160 /* Debug */ = { + OBJ_173 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = OBJ_8 /* AsyncObjects.xcconfig */; buildSettings = { @@ -856,7 +903,7 @@ }; name = Debug; }; - OBJ_161 /* Release */ = { + OBJ_174 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = OBJ_8 /* AsyncObjects.xcconfig */; buildSettings = { @@ -887,7 +934,7 @@ }; name = Release; }; - OBJ_216 /* Debug */ = { + OBJ_229 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { LD = /usr/bin/true; @@ -896,7 +943,7 @@ }; name = Debug; }; - OBJ_217 /* Release */ = { + OBJ_230 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { LD = /usr/bin/true; @@ -961,47 +1008,47 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - OBJ_104 /* Build configuration list for PBXNativeTarget "AsyncObjects" */ = { + OBJ_112 /* Build configuration list for PBXNativeTarget "AsyncObjects" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_105 /* Debug */, - OBJ_106 /* Release */, + OBJ_113 /* Debug */, + OBJ_114 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - OBJ_126 /* Build configuration list for PBXNativeTarget "AsyncObjectsPackageDescription" */ = { + OBJ_139 /* Build configuration list for PBXNativeTarget "AsyncObjectsPackageDescription" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_127 /* Debug */, - OBJ_128 /* Release */, + OBJ_140 /* Debug */, + OBJ_141 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - OBJ_132 /* Build configuration list for PBXAggregateTarget "AsyncObjectsPackageTests" */ = { + OBJ_145 /* Build configuration list for PBXAggregateTarget "AsyncObjectsPackageTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_133 /* Debug */, - OBJ_134 /* Release */, + OBJ_146 /* Debug */, + OBJ_147 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - OBJ_137 /* Build configuration list for PBXNativeTarget "AsyncObjectsTests" */ = { + OBJ_150 /* Build configuration list for PBXNativeTarget "AsyncObjectsTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_138 /* Debug */, - OBJ_139 /* Release */, + OBJ_151 /* Debug */, + OBJ_152 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - OBJ_159 /* Build configuration list for PBXNativeTarget "OrderedCollections" */ = { + OBJ_172 /* Build configuration list for PBXNativeTarget "OrderedCollections" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_160 /* Debug */, - OBJ_161 /* Release */, + OBJ_173 /* Debug */, + OBJ_174 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -1015,11 +1062,11 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - OBJ_215 /* Build configuration list for PBXNativeTarget "swift-collectionsPackageDescription" */ = { + OBJ_228 /* Build configuration list for PBXNativeTarget "swift-collectionsPackageDescription" */ = { isa = XCConfigurationList; buildConfigurations = ( - OBJ_216 /* Debug */, - OBJ_217 /* Release */, + OBJ_229 /* Debug */, + OBJ_230 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/Sources/AsyncObjects/AsyncCountdownEvent.swift b/Sources/AsyncObjects/AsyncCountdownEvent.swift index 03c5b7f9..c1445d08 100644 --- a/Sources/AsyncObjects/AsyncCountdownEvent.swift +++ b/Sources/AsyncObjects/AsyncCountdownEvent.swift @@ -35,7 +35,7 @@ import OrderedCollections /// /// Use the ``limit`` parameter to indicate concurrent low priority usage, i.e. if limit set to zero, /// only one low priority usage allowed at one time. -public actor AsyncCountdownEvent: AsyncObject { +public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { /// The suspended tasks continuation type. @usableFromInline internal typealias Continuation = SafeContinuation< @@ -130,28 +130,6 @@ public actor AsyncCountdownEvent: AsyncObject { } } - /// Suspends the current task, then calls the given closure with a throwing continuation for the current task. - /// Continuation can be cancelled with error if current task is cancelled, by invoking `_removeContinuation`. - /// - /// Spins up a new continuation and requests to track it with key by invoking `_addContinuation`. - /// This operation cooperatively checks for cancellation and reacting to it by invoking `_removeContinuation`. - /// Continuation can be resumed with error and some cleanup code can be run here. - /// - /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. - @inlinable - internal nonisolated func _withPromisedContinuation() async throws { - let key = UUID() - try await Continuation.withCancellation(synchronizedWith: locker) { - Task { [weak self] in - await self?._removeContinuation(withKey: key) - } - } operation: { continuation in - Task { [weak self] in - await self?._addContinuation(continuation, withKey: key) - } - } - } - /// Increments the countdown event current count by the specified value. /// /// - Parameter count: The value by which to increase ``currentCount``. diff --git a/Sources/AsyncObjects/AsyncEvent.swift b/Sources/AsyncObjects/AsyncEvent.swift index eb55f5cc..79e0577d 100644 --- a/Sources/AsyncObjects/AsyncEvent.swift +++ b/Sources/AsyncObjects/AsyncEvent.swift @@ -23,7 +23,7 @@ import Foundation /// // signal event after completing some task /// event.signal() /// ``` -public actor AsyncEvent: AsyncObject { +public actor AsyncEvent: AsyncObject, ContinuableCollection { /// The suspended tasks continuation type. @usableFromInline internal typealias Continuation = SafeContinuation< @@ -65,28 +65,6 @@ public actor AsyncEvent: AsyncObject { continuations.removeValue(forKey: key) } - /// Suspends the current task, then calls the given closure with a throwing continuation for the current task. - /// Continuation can be cancelled with error if current task is cancelled, by invoking `_removeContinuation`. - /// - /// Spins up a new continuation and requests to track it with key by invoking `_addContinuation`. - /// This operation cooperatively checks for cancellation and reacting to it by invoking `_removeContinuation`. - /// Continuation can be resumed with error and some cleanup code can be run here. - /// - /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. - @inlinable - internal nonisolated func _withPromisedContinuation() async throws { - let key = UUID() - try await Continuation.withCancellation(synchronizedWith: locker) { - Task { [weak self] in - await self?._removeContinuation(withKey: key) - } - } operation: { continuation in - Task { [weak self] in - await self?._addContinuation(continuation, withKey: key) - } - } - } - /// Resets signal of event. @inlinable internal func _reset() { diff --git a/Sources/AsyncObjects/AsyncSemaphore.swift b/Sources/AsyncObjects/AsyncSemaphore.swift index 96018a5b..9a3072db 100644 --- a/Sources/AsyncObjects/AsyncSemaphore.swift +++ b/Sources/AsyncObjects/AsyncSemaphore.swift @@ -25,7 +25,7 @@ import OrderedCollections /// // release after executing critical async tasks /// defer { semaphore.signal() } /// ``` -public actor AsyncSemaphore: AsyncObject { +public actor AsyncSemaphore: AsyncObject, ContinuableCollection { /// The suspended tasks continuation type. @usableFromInline internal typealias Continuation = SafeContinuation< @@ -85,28 +85,6 @@ public actor AsyncSemaphore: AsyncObject { count += 1 } - /// Suspends the current task, then calls the given closure with a throwing continuation for the current task. - /// Continuation can be cancelled with error if current task is cancelled, by invoking `_removeContinuation`. - /// - /// Spins up a new continuation and requests to track it with key by invoking `_addContinuation`. - /// This operation cooperatively checks for cancellation and reacting to it by invoking `_removeContinuation`. - /// Continuation can be resumed with error and some cleanup code can be run here. - /// - /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. - @inlinable - internal nonisolated func _withPromisedContinuation() async throws { - let key = UUID() - try await Continuation.withCancellation(synchronizedWith: locker) { - Task { [weak self] in - await self?._removeContinuation(withKey: key) - } - } operation: { continuation in - Task { [weak self] in - await self?._addContinuation(continuation, withKey: key) - } - } - } - /// Signals (increments) and releases a semaphore. @inlinable internal func _signal() { diff --git a/Sources/AsyncObjects/Continuable.swift b/Sources/AsyncObjects/Continuation/Continuable.swift similarity index 51% rename from Sources/AsyncObjects/Continuable.swift rename to Sources/AsyncObjects/Continuation/Continuable.swift index b7287b46..62bf675d 100644 --- a/Sources/AsyncObjects/Continuable.swift +++ b/Sources/AsyncObjects/Continuation/Continuable.swift @@ -4,6 +4,7 @@ /// /// Use continuations for interfacing Swift tasks with event loops, delegate methods, callbacks, /// and other non-async scheduling mechanisms. +@rethrows public protocol Continuable { /// The type of value to resume the continuation with in case of success. associatedtype Success @@ -29,7 +30,8 @@ public protocol Continuable { /// /// Use non-throwing continuation to interface synchronous code that might fail, /// or implements task cancellation mechanism, with asynchronous code. -public protocol ThrowingContinuable: Continuable +@rethrows +internal protocol ThrowingContinuable: Continuable where Failure == Error { /// Suspends the current task, then calls the given closure /// with a throwing continuation for the current task. @@ -42,15 +44,14 @@ where Failure == Error { /// that is the notional source for the continuation, /// used to identify the continuation in runtime diagnostics /// related to misuse of this continuation. - /// - fn: A closure that takes the throwing continuation parameter. - /// You can resume the continuation exactly once. + /// - body: A closure that takes the throwing continuation parameter. + /// You can resume the continuation exactly once. /// /// - Returns: The value passed to the continuation by the closure. /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. - @inlinable static func with( function: String, - _ fn: (Self) -> Void + _ body: (Self) -> Void ) async throws -> Success } @@ -58,7 +59,8 @@ where Failure == Error { /// by representing task state and allowing task to be always resumed with some value. /// /// Use non-throwing continuation to interface synchronous code that never fails with asynchronous code. -public protocol NonThrowingContinuable: Continuable +@rethrows +internal protocol NonThrowingContinuable: Continuable where Failure == Never { /// Suspends the current task, then calls the given closure /// with a non-throwing continuation for the current task. @@ -71,14 +73,13 @@ where Failure == Never { /// that is the notional source for the continuation, /// used to identify the continuation in runtime diagnostics /// related to misuse of this continuation. - /// - fn: A closure that takes the non-throwing continuation parameter. - /// You can resume the continuation exactly once. + /// - body: A closure that takes the non-throwing continuation parameter. + /// You can resume the continuation exactly once. /// /// - Returns: The value passed to the continuation by the closure. - @inlinable static func with( function: String, - _ fn: (Self) -> Void + _ body: (Self) -> Void ) async -> Success } #else @@ -87,6 +88,7 @@ where Failure == Never { /// /// Use continuations for interfacing Swift tasks with event loops, delegate methods, callbacks, /// and other non-async scheduling mechanisms. +@rethrows public protocol Continuable { /// The type of value to resume the continuation with in case of success. associatedtype Success @@ -112,7 +114,8 @@ public protocol Continuable { /// /// Use non-throwing continuation to interface synchronous code that might fail, /// or implements task cancellation mechanism, with asynchronous code. -public protocol ThrowingContinuable: Continuable where Failure == Error { +@rethrows +internal protocol ThrowingContinuable: Continuable where Failure == Error { /// Suspends the current task, then calls the given closure /// with a throwing continuation for the current task. /// @@ -124,15 +127,14 @@ public protocol ThrowingContinuable: Continuable where Failure == Error { /// that is the notional source for the continuation, /// used to identify the continuation in runtime diagnostics /// related to misuse of this continuation. - /// - fn: A closure that takes the throwing continuation parameter. - /// You can resume the continuation exactly once. + /// - body: A closure that takes the throwing continuation parameter. + /// You can resume the continuation exactly once. /// /// - Returns: The value passed to the continuation by the closure. /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. - @inlinable static func with( function: String, - _ fn: (Self) -> Void + _ body: (Self) -> Void ) async throws -> Success } @@ -140,7 +142,8 @@ public protocol ThrowingContinuable: Continuable where Failure == Error { /// by representing task state and allowing task to be always resumed with some value. /// /// Use non-throwing continuation to interface synchronous code that never fails with asynchronous code. -public protocol NonThrowingContinuable: Continuable where Failure == Never { +@rethrows +internal protocol NonThrowingContinuable: Continuable where Failure == Never { /// Suspends the current task, then calls the given closure /// with a non-throwing continuation for the current task. /// @@ -152,153 +155,60 @@ public protocol NonThrowingContinuable: Continuable where Failure == Never { /// that is the notional source for the continuation, /// used to identify the continuation in runtime diagnostics /// related to misuse of this continuation. - /// - fn: A closure that takes the non-throwing continuation parameter. - /// You can resume the continuation exactly once. + /// - body: A closure that takes the non-throwing continuation parameter. + /// You can resume the continuation exactly once. /// /// - Returns: The value passed to the continuation by the closure. - @inlinable static func with( function: String, - _ fn: (Self) -> Void + _ body: (Self) -> Void ) async -> Success } #endif -public extension Continuable where Failure == Error { - /// Cancel continuation by resuming with cancellation error. +public extension Continuable { + /// Dummy cancellation method for continuations + /// that don't support cancellation. @inlinable - func cancel() { self.resume(throwing: CancellationError()) } -} + func cancel() { /* Do nothing */ } -#if DEBUG || ASYNCOBJECTS_USE_CHECKEDCONTINUATION -/// The continuation type used in ``AsyncObjects`` package. -/// -/// In `DEBUG` mode or if `ASYNCOBJECTS_USE_CHECKEDCONTINUATION` flag turned on -/// `CheckedContinuation` is used. -/// -/// In `RELEASE` mode and in absence of `ASYNCOBJECTS_USE_CHECKEDCONTINUATION` -/// flag `UnsafeContinuation` is used. -public typealias GlobalContinuation = CheckedContinuation - -extension CheckedContinuation: Continuable {} - -extension CheckedContinuation: ThrowingContinuable where E == Error { - /// Suspends the current task, then calls the given closure - /// with a checked throwing continuation for the current task. + /// Resume the task awaiting the continuation by having it return normally from its suspension point. /// - /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. - /// `CheckedContinuation` logs messages proving additional info on these errors. - /// Once all errors resolved, use `UnsafeContinuation` in release mode to benefit improved performance - /// at the loss of additional runtime checks. + /// A continuation must be resumed at least once. /// - /// - Parameters: - /// - function: A string identifying the declaration - /// that is the notional source for the continuation, - /// used to identify the continuation in runtime diagnostics - /// related to misuse of this continuation. - /// - fn: A closure that takes a `CheckedContinuation` parameter. - /// You must resume the continuation exactly once. + /// After calling this method, control immediately returns to the caller. + /// The task continues executing when its executor schedules it. /// - /// - Returns: The value passed to the continuation by the closure. - /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. - @inlinable - public static func with( - function: String = #function, - _ body: (CheckedContinuation) -> Void - ) async throws -> T { - return try await withCheckedThrowingContinuation( - function: function, - body - ) + /// - Parameter value: The value to return from the continuation. + func resume(returning value: Success) { + self.resume(with: .success(value)) } -} -extension CheckedContinuation: NonThrowingContinuable where E == Never { - /// Suspends the current task, then calls the given closure - /// with a checked non-throwing continuation for the current task. - /// - /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. - /// `CheckedContinuation` logs messages proving additional info on these errors. - /// Once all errors resolved, use `UnsafeContinuation` in release mode to benefit improved performance - /// at the loss of additional runtime checks. + /// Resume the task that’s awaiting the continuation by returning. /// - /// - Parameters: - /// - function: A string identifying the declaration - /// that is the notional source for the continuation, - /// used to identify the continuation in runtime diagnostics - /// related to misuse of this continuation. - /// - fn: A closure that takes a `CheckedContinuation` parameter. - /// You must resume the continuation exactly once. + /// A continuation must be resumed at least once. /// - /// - Returns: The value passed to the continuation by the closure. - @inlinable - public static func with( - function: String = #function, - _ body: (CheckedContinuation) -> Void - ) async -> T { - return await withCheckedContinuation(function: function, body) + /// After calling this method, control immediately returns to the caller. + /// The task continues executing when its executor schedules it. + func resume(returning value: Success = ()) where Success == Void { + self.resume(with: .success(value)) } -} -#else -/// The continuation type used in ``AsyncObjects`` package. -/// -/// In `DEBUG` mode or if `ASYNCOBJECTS_USE_CHECKEDCONTINUATION` flag turned on -/// `CheckedContinuation` is used. -/// -/// In `RELEASE` mode and in absence of `ASYNCOBJECTS_USE_CHECKEDCONTINUATION` -/// flag `UnsafeContinuation` is used. -public typealias GlobalContinuation = UnsafeContinuation - -extension UnsafeContinuation: Continuable {} -extension UnsafeContinuation: ThrowingContinuable where E == Error { - /// Suspends the current task, then calls the given closure - /// with an unsafe throwing continuation for the current task. + /// Resume the task awaiting the continuation by having it throw an error from its suspension point. /// - /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. - /// Use `CheckedContinuation` to capture relevant data in case of runtime errors. + /// A continuation must be resumed at least once. /// - /// - Parameters: - /// - function: A string identifying the declaration - /// that is the notional source for the continuation, - /// used to identify the continuation in runtime diagnostics - /// related to misuse of this continuation. - /// - fn: A closure that takes an `UnsafeContinuation` parameter. - /// You must resume the continuation exactly once. + /// After calling this method, control immediately returns to the caller. + /// The task continues executing when its executor schedules it. /// - /// - Returns: The value passed to the continuation by the closure. - /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. - @inlinable - public static func with( - function: String = #function, - _ fn: (UnsafeContinuation) -> Void - ) async throws -> T { - return try await withUnsafeThrowingContinuation(fn) + /// - Parameter error: The error to throw from the continuation. + func resume(throwing error: Failure) { + self.resume(with: .failure(error)) } } -extension UnsafeContinuation: NonThrowingContinuable where E == Never { - /// Suspends the current task, then calls the given closure - /// with an unsafe non-throwing continuation for the current task. - /// - /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. - /// Use `CheckedContinuation` to capture relevant data in case of runtime errors. - /// - /// - Parameters: - /// - function: A string identifying the declaration - /// that is the notional source for the continuation, - /// used to identify the continuation in runtime diagnostics - /// related to misuse of this continuation. - /// - fn: A closure that takes an `UnsafeContinuation` parameter. - /// You must resume the continuation exactly once. - /// - /// - Returns: The value passed to the continuation by the closure. +public extension Continuable where Failure == Error { + /// Cancel continuation by resuming with cancellation error. @inlinable - public static func with( - function: String = #function, - _ fn: (UnsafeContinuation) -> Void - ) async -> T { - return await withUnsafeContinuation(fn) - } + func cancel() { self.resume(throwing: CancellationError()) } } -#endif diff --git a/Sources/AsyncObjects/Continuation/ContinuableCollection.swift b/Sources/AsyncObjects/Continuation/ContinuableCollection.swift new file mode 100644 index 00000000..4e18620e --- /dev/null +++ b/Sources/AsyncObjects/Continuation/ContinuableCollection.swift @@ -0,0 +1,79 @@ +#if swift(>=5.7) +import Foundation +#else +@preconcurrency import Foundation +#endif + +/// A type that manages a collection of continuations with an associated key. +/// +/// A MUTual EXclusion object is used to synchronize continuations state. +@rethrows +internal protocol ContinuableCollection { + /// The continuation item type in collection. + associatedtype Continuation: Continuable + /// The key type that is associated with each continuation item. + associatedtype Key: Hashable + /// The MUTual EXclusion object type used + /// to synchronize continuation state. + associatedtype Lock: Exclusible + + /// The MUTual EXclusion object used + /// to synchronize continuation state. + var locker: Lock { get } + /// Add continuation with the provided key to collection for tracking. + /// + /// - Parameters: + /// - continuation: The continuation value to add + /// - key: The key to associate continuation with. + func _addContinuation(_ continuation: Continuation, withKey key: Key) async + /// Remove continuation with the associated key from collection out of tracking. + /// + /// - Parameter key: The key for continuation to remove. + func _removeContinuation(withKey key: Key) async + /// Suspends the current task, then calls the given closure with a continuation for the current task. + /// + /// - Returns: The value continuation is resumed with. + /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. + func _withPromisedContinuation() async rethrows -> Continuation.Success +} + +extension ContinuableCollection { + /// Remove continuation associated with provided key. + /// + /// Default implementation that does nothing. + /// + /// - Parameter key: The key for continuation to remove. + func _removeContinuation(withKey key: Key) async { /* Do nothing */ } +} + +extension ContinuableCollection +where + Self: AnyObject, Self: Sendable, Continuation: SynchronizedContinuable, + Continuation: Sendable, Continuation.Value: ThrowingContinuable, + Continuation.Lock == Lock, Key == UUID +{ + /// Suspends the current task, then calls the given closure with a throwing continuation for the current task. + /// Continuation can be cancelled with error if current task is cancelled, by invoking `_removeContinuation`. + /// + /// Spins up a new continuation and requests to track it with key by invoking `_addContinuation`. + /// This operation cooperatively checks for cancellation and reacting to it by invoking `_removeContinuation`. + /// Continuation can be resumed with error and some cleanup code can be run here. + /// + /// - Returns: The value continuation is resumed with. + /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. + @inlinable + func _withPromisedContinuation() async rethrows -> Continuation.Success { + let key = UUID() + return try await Continuation.withCancellation( + synchronizedWith: locker + ) { + Task { [weak self] in + await self?._removeContinuation(withKey: key) + } + } operation: { continuation in + Task { [weak self] in + await self?._addContinuation(continuation, withKey: key) + } + } + } +} diff --git a/Sources/AsyncObjects/Continuation/GlobalContinuation.swift b/Sources/AsyncObjects/Continuation/GlobalContinuation.swift new file mode 100644 index 00000000..96a4ee82 --- /dev/null +++ b/Sources/AsyncObjects/Continuation/GlobalContinuation.swift @@ -0,0 +1,132 @@ +#if DEBUG || ASYNCOBJECTS_USE_CHECKEDCONTINUATION +/// The continuation type used in ``AsyncObjects`` package. +/// +/// In `DEBUG` mode or if `ASYNCOBJECTS_USE_CHECKEDCONTINUATION` flag turned on +/// `CheckedContinuation` is used. +/// +/// In `RELEASE` mode and in absence of `ASYNCOBJECTS_USE_CHECKEDCONTINUATION` +/// flag `UnsafeContinuation` is used. +public typealias GlobalContinuation = CheckedContinuation + +extension CheckedContinuation: Continuable {} + +extension CheckedContinuation: ThrowingContinuable where E == Error { + /// Suspends the current task, then calls the given closure + /// with a checked throwing continuation for the current task. + /// + /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. + /// `CheckedContinuation` logs messages proving additional info on these errors. + /// Once all errors resolved, use `UnsafeContinuation` in release mode to benefit improved performance + /// at the loss of additional runtime checks. + /// + /// - Parameters: + /// - function: A string identifying the declaration + /// that is the notional source for the continuation, + /// used to identify the continuation in runtime diagnostics + /// related to misuse of this continuation. + /// - body: A closure that takes a `CheckedContinuation` parameter. + /// You must resume the continuation exactly once. + /// + /// - Returns: The value passed to the continuation by the closure. + /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. + @inlinable + public static func with( + function: String = #function, + _ body: (Self) -> Void + ) async throws -> T { + return try await withCheckedThrowingContinuation( + function: function, + body + ) + } +} + +extension CheckedContinuation: NonThrowingContinuable where E == Never { + /// Suspends the current task, then calls the given closure + /// with a checked non-throwing continuation for the current task. + /// + /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. + /// `CheckedContinuation` logs messages proving additional info on these errors. + /// Once all errors resolved, use `UnsafeContinuation` in release mode to benefit improved performance + /// at the loss of additional runtime checks. + /// + /// - Parameters: + /// - function: A string identifying the declaration + /// that is the notional source for the continuation, + /// used to identify the continuation in runtime diagnostics + /// related to misuse of this continuation. + /// - body: A closure that takes a `CheckedContinuation` parameter. + /// You must resume the continuation exactly once. + /// + /// - Returns: The value passed to the continuation by the closure. + @inlinable + public static func with( + function: String = #function, + _ body: (Self) -> Void + ) async -> T { + return await withCheckedContinuation(function: function, body) + } +} +#else +/// The continuation type used in ``AsyncObjects`` package. +/// +/// In `DEBUG` mode or if `ASYNCOBJECTS_USE_CHECKEDCONTINUATION` flag turned on +/// `CheckedContinuation` is used. +/// +/// In `RELEASE` mode and in absence of `ASYNCOBJECTS_USE_CHECKEDCONTINUATION` +/// flag `UnsafeContinuation` is used. +public typealias GlobalContinuation = UnsafeContinuation + +extension UnsafeContinuation: Continuable {} + +extension UnsafeContinuation: ThrowingContinuable where E == Error { + /// Suspends the current task, then calls the given closure + /// with an unsafe throwing continuation for the current task. + /// + /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. + /// Use `CheckedContinuation` to capture relevant data in case of runtime errors. + /// + /// - Parameters: + /// - function: A string identifying the declaration + /// that is the notional source for the continuation, + /// used to identify the continuation in runtime diagnostics + /// related to misuse of this continuation. + /// - body: A closure that takes an `UnsafeContinuation` parameter. + /// You must resume the continuation exactly once. + /// + /// - Returns: The value passed to the continuation by the closure. + /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. + @inlinable + public static func with( + function: String = #function, + _ body: (Self) -> Void + ) async throws -> T { + return try await withUnsafeThrowingContinuation(body) + } +} + +extension UnsafeContinuation: NonThrowingContinuable where E == Never { + /// Suspends the current task, then calls the given closure + /// with an unsafe non-throwing continuation for the current task. + /// + /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. + /// Use `CheckedContinuation` to capture relevant data in case of runtime errors. + /// + /// - Parameters: + /// - function: A string identifying the declaration + /// that is the notional source for the continuation, + /// used to identify the continuation in runtime diagnostics + /// related to misuse of this continuation. + /// - body: A closure that takes an `UnsafeContinuation` parameter. + /// You must resume the continuation exactly once. + /// + /// - Returns: The value passed to the continuation by the closure. + @inlinable + public static func with( + function: String = #function, + _ body: (Self) -> Void + ) async -> T { + return await withUnsafeContinuation(body) + } +} +#endif diff --git a/Sources/AsyncObjects/SafeContinuation.swift b/Sources/AsyncObjects/Continuation/SafeContinuation.swift similarity index 51% rename from Sources/AsyncObjects/SafeContinuation.swift rename to Sources/AsyncObjects/Continuation/SafeContinuation.swift index 5a9d3dcf..375dcfc7 100644 --- a/Sources/AsyncObjects/SafeContinuation.swift +++ b/Sources/AsyncObjects/Continuation/SafeContinuation.swift @@ -11,13 +11,13 @@ /// Only first resume operation is considered and rest are all ignored. /// While there is no checks for missing resume operations, /// `CheckedContinuation` can be used as underlying continuation value for additional runtime checks. -public final class SafeContinuation: Continuable { +@usableFromInline +internal final class SafeContinuation: SynchronizedContinuable { /// Tracks the status of continuation resuming for ``SafeContinuation``. /// /// Depending upon ``SafeContinuation`` status the ``SafeContinuation/resume(with:)`` /// invocation effect is determined. Only first resume operation is considered and rest are all ignored. - @frozen - public enum Status { + enum Status { /// Indicates continuation is waiting to be resumed. /// /// Resuming ``SafeContinuation`` with this status returns control immediately to the caller. @@ -54,7 +54,8 @@ public final class SafeContinuation: Continuable { /// Checks whether continuation is already resumed /// or to be resumed with provided value. - public var resumed: Bool { + @usableFromInline + var resumed: Bool { return locker.perform { switch status { case .waiting: @@ -68,6 +69,9 @@ public final class SafeContinuation: Continuable { /// Creates a safe continuation from provided continuation. /// + /// The provided platform lock is used to synchronize + /// continuation state. + /// /// - Parameters: /// - status: The initial ``Status`` of provided continuation. /// - value: The continuation value to store. After passing the continuation @@ -78,7 +82,7 @@ public final class SafeContinuation: Continuable { /// - Returns: The newly created safe continuation. /// - Important: After passing the continuation with this method, /// don’t use it outside of this object. - public init( + init( status: Status = .waiting, with value: C? = nil, synchronizedWith locker: Locker = .init() @@ -94,6 +98,25 @@ public final class SafeContinuation: Continuable { } } + /// Creates a safe continuation from provided continuation. + /// + /// The provided platform lock is used to synchronize + /// continuation state. + /// + /// - Parameters: + /// - value: The continuation value to store. After passing the continuation + /// with this method, don’t use it outside of this object. + /// - locker: The platform lock to use to synchronize continuation state. + /// New lock object is created in case none provided. + /// + /// - Returns: The newly created safe continuation. + /// - Important: After passing the continuation with this method, + /// don’t use it outside of this object. + @usableFromInline + convenience init(with value: C?, synchronizedWith locker: Locker) { + self.init(status: .waiting, with: value, synchronizedWith: locker) + } + /// Store the provided continuation if no continuation was provided during initialization. /// /// Use this method to pass continuation if continuation can't be provided during initialization. @@ -111,7 +134,7 @@ public final class SafeContinuation: Continuable { /// /// - Important: After passing the continuation with this method, /// don’t use it outside of this object. - public func add( + func add( continuation: C, status: Status = .waiting, file: StaticString = #file, @@ -137,41 +160,20 @@ public final class SafeContinuation: Continuable { } } - /// Resume the task awaiting the continuation by having it return normally from its suspension point. - /// - /// A continuation must be resumed at least once. If the continuation has already resumed, - /// then calling this method has no effect. - /// - /// After calling this method, control immediately returns to the caller. - /// The task continues executing when its executor schedules it. - /// - /// - Parameter value: The value to return from the continuation. - public func resume(returning value: C.Success) { - self.resume(with: .success(value)) - } - - /// Resume the task that’s awaiting the continuation by returning. - /// - /// A continuation must be resumed at least once. If the continuation has already resumed, - /// then calling this method has no effect. - /// - /// After calling this method, control immediately returns to the caller. - /// The task continues executing when its executor schedules it. - public func resume() where C.Success == Void { - self.resume(returning: ()) - } - - /// Resume the task awaiting the continuation by having it throw an error from its suspension point. + /// Store the provided continuation if no continuation was provided during initialization. /// - /// A continuation must be resumed at least once. If the continuation has already resumed, - /// then calling this method has no effect. + /// Use this method to pass continuation if continuation can't be provided during initialization. + /// If continuation provided already during initialization, invoking this method will cause runtime exception. /// - /// After calling this method, control immediately returns to the caller. - /// The task continues executing when its executor schedules it. + /// - Parameters: + /// - continuation: The continuation value to store. After passing the continuation + /// with this method, don’t use it outside of this object. /// - /// - Parameter error: The error to throw from the continuation. - public func resume(throwing error: C.Failure) { - self.resume(with: .failure(error)) + /// - Important: After passing the continuation with this method, + /// don’t use it outside of this object. + @usableFromInline + func add(continuation: C) { + self.add(continuation: continuation, status: .waiting) } /// Resume the task awaiting the continuation by having it either return normally @@ -184,7 +186,8 @@ public final class SafeContinuation: Continuable { /// The task continues executing when its executor schedules it. /// /// - Parameter result: A value to either return or throw from the continuation. - public func resume(with result: Result) { + @usableFromInline + func resume(with result: Result) { locker.perform { let finalResult: Result switch (status, value) { @@ -204,102 +207,5 @@ public final class SafeContinuation: Continuable { } } -extension SafeContinuation: ThrowingContinuable where C: ThrowingContinuable { - /// Suspends the current task, then calls the given operation with a ``SafeContinuation`` - /// for the current task with a cancellation handler that’s immediately invoked if the current task is canceled. - /// - /// This differs from the operation cooperatively checking for cancellation and reacting to it in that - /// the cancellation handler is always and immediately invoked after resuming continuation with - /// `CancellationError` when the task is canceled. For example, even if the operation - /// is running code that never checks for cancellation and provided continuation to operation hasn't been resumed, - /// a cancellation handler still runs cancelling the continuation and provides a chance to run some cleanup code. - /// - /// - Parameters: - /// - locker: The platform lock to use to synchronize continuation state. - /// New lock object is created in case none provided. - /// - function: A string identifying the declaration - /// that is the notional source for the continuation, - /// used to identify the continuation in runtime diagnostics - /// related to misuse of this continuation. - /// - handler: A handler immediately invoked if task is cancelled. - /// - operation: A closure that takes an ``SafeContinuation`` parameter. - /// You must resume the continuation at least once. - /// - /// - Returns: The value passed to the continuation by the operation. - /// - Throws: If cancelled or `resume(throwing:)` is called on the continuation, - /// this function throws that error. - public static func withCancellation( - synchronizedWith locker: Locker = .init(), - function: String = #function, - handler: @escaping @Sendable () -> Void, - operation: @escaping (SafeContinuation) -> Void - ) async throws -> C.Success where C.Success: Sendable { - let safeContinuation = SafeContinuation(synchronizedWith: locker) - return try await withTaskCancellationHandler { - return try await C.with(function: function) { continuation in - safeContinuation.add(continuation: continuation) - operation(safeContinuation) - } - } onCancel: { [weak safeContinuation] in - safeContinuation?.cancel() - handler() - } - } - - /// Suspends the current task, then calls the given closure with a ``SafeContinuation`` for the current task. - /// - /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. - /// ``SafeContinuation`` allows accessing and resuming the same continuations in concurrent code - /// by keeping track of underlying continuation value state. - /// - /// - Parameters: - /// - function: A string identifying the declaration - /// that is the notional source for the continuation, - /// used to identify the continuation in runtime diagnostics - /// related to misuse of this continuation. - /// - fn: A closure that takes a ``SafeContinuation`` parameter. - /// You must resume the continuation at least once. - /// - /// - Returns: The value passed to the continuation by the closure. - /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. - @inlinable - public static func with( - function: String = #function, - _ body: (SafeContinuation) -> Void - ) async throws -> C.Success { - return try await C.with(function: function) { continuation in - body(SafeContinuation(with: continuation)) - } - } -} - -extension SafeContinuation: NonThrowingContinuable -where C: NonThrowingContinuable { - /// Suspends the current task, then calls the given closure with a ``SafeContinuation`` for the current task. - /// - /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. - /// ``SafeContinuation`` allows accessing and resuming the same continuations in concurrent code - /// by keeping track of underlying continuation value state. - /// - /// - Parameters: - /// - function: A string identifying the declaration - /// that is the notional source for the continuation, - /// used to identify the continuation in runtime diagnostics - /// related to misuse of this continuation. - /// - fn: A closure that takes a ``SafeContinuation`` parameter. - /// You must resume the continuation exactly once. - /// - /// - Returns: The value passed to the continuation by the closure. - @inlinable - public static func with( - function: String = #function, - _ body: (SafeContinuation) -> Void - ) async -> C.Success { - return await C.with(function: function) { continuation in - body(SafeContinuation(with: continuation)) - } - } -} - extension SafeContinuation: @unchecked Sendable where C.Success: Sendable {} extension SafeContinuation.Status: Sendable where C.Success: Sendable {} diff --git a/Sources/AsyncObjects/Continuation/SynchronizedContinuable.swift b/Sources/AsyncObjects/Continuation/SynchronizedContinuable.swift new file mode 100644 index 00000000..be3fe55d --- /dev/null +++ b/Sources/AsyncObjects/Continuation/SynchronizedContinuable.swift @@ -0,0 +1,189 @@ +/// A type that allows to interface between synchronous and asynchronous code, +/// by representing synchronized task state and allowing exclusive task resuming +/// with some value or error. +/// +/// Use synchronized continuations for interfacing Swift tasks with event loops, +/// delegate methods, callbacks, and other non-async scheduling mechanisms +/// where continuations have chance to be resumed multiple times. +@rethrows +@usableFromInline +internal protocol SynchronizedContinuable: Continuable { + /// The MUTual EXclusion object type used + /// to synchronize continuation state. + associatedtype Lock: Exclusible + /// The actual continuation value type. + associatedtype Value: Continuable + where Value.Success == Success, Value.Failure == Failure + + /// Creates a synchronized continuation from provided continuation. + /// + /// The provided MUTual EXclusion object is used to synchronize + /// continuation state. + /// + /// - Parameters: + /// - value: The continuation value to store. After passing the continuation + /// with this method, don’t use it outside of this object. + /// - locker: The MUTual EXclusion object to use to synchronize continuation state. + /// + /// - Returns: The newly created synchronized continuation. + /// - Important: After passing the continuation with this method, + /// don’t use it outside of this object. + init(with value: Value?, synchronizedWith locker: Lock) + /// Store the provided continuation if no continuation was provided during initialization. + /// + /// Use this method to pass continuation if continuation can't be provided during initialization. + /// + /// - Parameters: + /// - continuation: The continuation value to store. + /// After passing the continuation with this method, + /// don’t use it outside of this object. + /// + /// - Important: After passing the continuation with this method, + /// don’t use it outside of this object. + func add(continuation: Value) +} + +extension SynchronizedContinuable +where Self: Sendable, Value: ThrowingContinuable { + /// Suspends the current task, then calls the given operation with a `SynchronizedContinuable` + /// for the current task with a cancellation handler that’s immediately invoked if the current task is canceled. + /// + /// This differs from the operation cooperatively checking for cancellation and reacting to it in that + /// the cancellation handler is always and immediately invoked after resuming continuation with + /// `CancellationError` when the task is canceled. For example, even if the operation + /// is running code that never checks for cancellation and provided continuation to operation hasn't been resumed, + /// a cancellation handler still runs cancelling the continuation and provides a chance to run some cleanup code. + /// + /// - Parameters: + /// - locker: The platform lock to use to synchronize continuation state. + /// New lock object is created in case none provided. + /// - function: A string identifying the declaration + /// that is the notional source for the continuation, + /// used to identify the continuation in runtime diagnostics + /// related to misuse of this continuation. + /// - handler: A handler immediately invoked if task is cancelled. + /// - operation: A closure that takes an `SynchronizedContinuable` parameter. + /// You must resume the continuation at least once. + /// + /// - Returns: The value passed to the continuation by the operation. + /// - Throws: If cancelled or `resume(throwing:)` is called on the continuation, + /// this function throws that error. + @usableFromInline + static func withCancellation( + synchronizedWith locker: Lock = .init(), + function: String = #function, + handler: @escaping @Sendable () -> Void, + operation: @escaping (Self) -> Void + ) async rethrows -> Success { + let cancellable = Self.init(with: nil, synchronizedWith: locker) + return try await withTaskCancellationHandler { + return try await Value.with( + function: function + ) { continuation in + cancellable.add(continuation: continuation) + operation(cancellable) + } + } onCancel: { + cancellable.cancel() + handler() + } + } + + /// Suspends the current task, then calls the given closure with a `SynchronizedContinuable` for the current task. + /// + /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. + /// `SynchronizedContinuable` allows accessing and resuming the same continuations in concurrent code + /// by keeping track of underlying continuation value state. + /// + /// - Parameters: + /// - function: A string identifying the declaration + /// that is the notional source for the continuation, + /// used to identify the continuation in runtime diagnostics + /// related to misuse of this continuation. + /// - body: A closure that takes a `SynchronizedContinuable` parameter. + /// You must resume the continuation at least once. + /// + /// - Returns: The value passed to the continuation by the closure. + /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. + @usableFromInline + static func with( + function: String = #function, + _ body: (Self) -> Void + ) async rethrows -> Success { + return try await Value.with(function: function) { continuation in + body(Self(with: continuation, synchronizedWith: .init())) + } + } +} + +extension SynchronizedContinuable +where Self: Sendable, Value: NonThrowingContinuable { + /// Suspends the current task, then calls the given operation with a `SynchronizedContinuable` + /// for the current task with a cancellation handler that’s immediately invoked if the current task is canceled. + /// + /// This differs from the operation cooperatively checking for cancellation and reacting to it in that + /// the cancellation handler is always and immediately invoked after resuming continuation with + /// `CancellationError` when the task is canceled. For example, even if the operation + /// is running code that never checks for cancellation and provided continuation to operation hasn't been resumed, + /// a cancellation handler still runs cancelling the continuation and provides a chance to run some cleanup code. + /// + /// - Parameters: + /// - locker: The platform lock to use to synchronize continuation state. + /// New lock object is created in case none provided. + /// - function: A string identifying the declaration + /// that is the notional source for the continuation, + /// used to identify the continuation in runtime diagnostics + /// related to misuse of this continuation. + /// - handler: A handler immediately invoked if task is cancelled. + /// - operation: A closure that takes an `SynchronizedContinuable` parameter. + /// You must resume the continuation at least once. + /// + /// - Returns: The value passed to the continuation by the operation. + /// - Throws: If cancelled or `resume(throwing:)` is called on the continuation, + /// this function throws that error. + @usableFromInline + internal static func withCancellation( + synchronizedWith locker: Lock = .init(), + function: String = #function, + handler: @escaping @Sendable () -> Void, + operation: @escaping (Self) -> Void + ) async -> Success { + let cancellable = Self.init(with: nil, synchronizedWith: locker) + return await withTaskCancellationHandler { + return await Value.with( + function: function + ) { continuation in + cancellable.add(continuation: continuation) + operation(cancellable) + } + } onCancel: { + cancellable.cancel() + handler() + } + } + + /// Suspends the current task, then calls the given closure with a `SynchronizedContinuable` for the current task. + /// + /// The continuation must be resumed exactly once, subsequent resumes will cause runtime error. + /// `SynchronizedContinuable` allows accessing and resuming the same continuations in concurrent code + /// by keeping track of underlying continuation value state. + /// + /// - Parameters: + /// - function: A string identifying the declaration + /// that is the notional source for the continuation, + /// used to identify the continuation in runtime diagnostics + /// related to misuse of this continuation. + /// - body: A closure that takes a `SynchronizedContinuable` parameter. + /// You must resume the continuation exactly once. + /// + /// - Returns: The value passed to the continuation by the closure. + @usableFromInline + internal static func with( + function: String = #function, + _ body: (Self) -> Void + ) async -> Success { + return await Value.with(function: function) { continuation in + body(Self(with: continuation, synchronizedWith: .init())) + } + } +} diff --git a/Sources/AsyncObjects/Task.swift b/Sources/AsyncObjects/Extensions/Task.swift similarity index 52% rename from Sources/AsyncObjects/Task.swift rename to Sources/AsyncObjects/Extensions/Task.swift index 034a68e1..2a030f56 100644 --- a/Sources/AsyncObjects/Task.swift +++ b/Sources/AsyncObjects/Extensions/Task.swift @@ -1,53 +1,3 @@ -public extension TaskGroup { - /// Adds a child task to the group and starts the task. - /// - /// This method adds child task to the group and returns only after the child task is started. - /// - /// - Parameters: - /// - priority: The priority of the operation task. Omit this parameter or - /// pass `nil` to set the child task’s priority to the priority of the group. - /// - operation: The operation to execute as part of the task group. - @inlinable - mutating func addTaskAndStart( - priority: TaskPriority? = nil, - operation: @escaping @Sendable () async -> ChildTaskResult - ) async { - typealias C = UnsafeContinuation - await withUnsafeContinuation { (continuation: C) in - self.addTask { - continuation.resume() - return await operation() - } - } - } -} - -public extension ThrowingTaskGroup { - /// Adds a child task to the group and starts the task. - /// - /// This method adds child task to the group and returns only after the child task is started. - /// This method doesn’t throw an error, even if the child task does. Instead, - /// the corresponding call to `ThrowingTaskGroup.next()` rethrows that error. - /// - /// - Parameters: - /// - priority: The priority of the operation task. Omit this parameter or - /// pass `nil` to set the child task’s priority to the priority of the group. - /// - operation: The operation to execute as part of the task group. - @inlinable - mutating func addTaskAndStart( - priority: TaskPriority? = nil, - operation: @escaping @Sendable () async throws -> ChildTaskResult - ) async { - typealias C = UnsafeContinuation - await withUnsafeContinuation { (continuation: C) in - self.addTask { - continuation.resume() - return try await operation() - } - } - } -} - public extension Task { /// Runs the given operation asynchronously as part of /// a new top-level cancellable task on behalf of the current actor. diff --git a/Sources/AsyncObjects/Extensions/TaskGroup.swift b/Sources/AsyncObjects/Extensions/TaskGroup.swift new file mode 100644 index 00000000..26017beb --- /dev/null +++ b/Sources/AsyncObjects/Extensions/TaskGroup.swift @@ -0,0 +1,49 @@ +public extension TaskGroup { + /// Adds a child task to the group and starts the task. + /// + /// This method adds child task to the group and returns only after the child task is started. + /// + /// - Parameters: + /// - priority: The priority of the operation task. Omit this parameter or + /// pass `nil` to set the child task’s priority to the priority of the group. + /// - operation: The operation to execute as part of the task group. + @inlinable + mutating func addTaskAndStart( + priority: TaskPriority? = nil, + operation: @escaping @Sendable () async -> ChildTaskResult + ) async { + typealias C = UnsafeContinuation + await withUnsafeContinuation { (continuation: C) in + self.addTask { + continuation.resume() + return await operation() + } + } + } +} + +public extension ThrowingTaskGroup { + /// Adds a child task to the group and starts the task. + /// + /// This method adds child task to the group and returns only after the child task is started. + /// This method doesn’t throw an error, even if the child task does. Instead, + /// the corresponding call to `ThrowingTaskGroup.next()` rethrows that error. + /// + /// - Parameters: + /// - priority: The priority of the operation task. Omit this parameter or + /// pass `nil` to set the child task’s priority to the priority of the group. + /// - operation: The operation to execute as part of the task group. + @inlinable + mutating func addTaskAndStart( + priority: TaskPriority? = nil, + operation: @escaping @Sendable () async throws -> ChildTaskResult + ) async { + typealias C = UnsafeContinuation + await withUnsafeContinuation { (continuation: C) in + self.addTask { + continuation.resume() + return try await operation() + } + } + } +} diff --git a/Sources/AsyncObjects/Future.swift b/Sources/AsyncObjects/Future.swift index 96166e50..80ac3c6d 100644 --- a/Sources/AsyncObjects/Future.swift +++ b/Sources/AsyncObjects/Future.swift @@ -204,11 +204,9 @@ extension Future where Failure == Never { /// This property exposes the fulfilled value for the `Future` asynchronously. /// Immediately returns if `Future` is fulfilled otherwise waits asynchronously /// for `Future` to be fulfilled. - public var value: Output { - get async { - if let result = result { return try! result.get() } - return await _withPromisedContinuation() - } + public func get() async -> Output { + if let result = result { return try! result.get() } + return await _withPromisedContinuation() } /// Combines into a single future, for all futures to be fulfilled. @@ -230,7 +228,12 @@ extension Future where Failure == Never { var result: [IndexedOutput] = [] result.reserveCapacity(futures.count) for (index, future) in futures.enumerated() { - group.addTask { (index: index, value: await future.value) } + group.addTask { + return ( + index: index, + value: await future.get() + ) + } } for await item in group { result.append(item) } promise( @@ -278,7 +281,10 @@ extension Future where Failure == Never { result.reserveCapacity(futures.count) for (index, future) in futures.enumerated() { group.addTask { - (index: index, value: .success(await future.value)) + return ( + index: index, + value: .success(await future.get()) + ) } } for await item in group { result.append(item) } @@ -322,7 +328,7 @@ extension Future where Failure == Never { return .init { promise in await withTaskGroup(of: Output.self) { group in futures.forEach { future in - group.addTask { await future.value } + group.addTask { await future.get() } } if let first = await group.next() { promise(.success(first)) @@ -422,11 +428,9 @@ extension Future where Failure == Error { /// the awaiting caller receives the error instead. /// /// - Throws: If future rejected with error or `CancellationError` if cancelled. - public var value: Output { - get async throws { - if let result = result { return try result.get() } - return try await _withPromisedContinuation() - } + public func get() async throws -> Output { + if let result = result { return try result.get() } + return try await _withPromisedContinuation() } /// Combines into a single future, for all futures to be fulfilled, or for any to be rejected. @@ -451,7 +455,7 @@ extension Future where Failure == Error { result.reserveCapacity(futures.count) for (index, future) in futures.enumerated() { group.addTask { - (index: index, value: try await future.value) + (index: index, value: try await future.get()) } } do { @@ -508,7 +512,7 @@ extension Future where Failure == Error { for (index, future) in futures.enumerated() { group.addTask { do { - let value = try await future.value + let value = try await future.get() return (index: index, value: .success(value)) } catch { return (index: index, value: .failure(error)) @@ -558,7 +562,7 @@ extension Future where Failure == Error { return .init { promise in await withThrowingTaskGroup(of: Output.self) { group in futures.forEach { future in - group.addTask { try await future.value } + group.addTask { try await future.get() } } do { if let first = try await group.next() { @@ -608,7 +612,7 @@ extension Future where Failure == Error { futures.forEach { future in group.addTask { do { - let value = try await future.value + let value = try await future.get() return .success(value) } catch { return .failure(error) diff --git a/Sources/AsyncObjects/Locks/Exclusible.swift b/Sources/AsyncObjects/Locks/Exclusible.swift new file mode 100644 index 00000000..f254db64 --- /dev/null +++ b/Sources/AsyncObjects/Locks/Exclusible.swift @@ -0,0 +1,23 @@ +/// A type that provides exclusive access to threads. +/// +/// The `perform(_:)` method executes a synchronous +/// piece of work exclusively for a single instance of this type. +@rethrows +@usableFromInline +internal protocol Exclusible { + /// Initializes a MUTual EXclusion object. + /// + /// - Returns: The newly created MUTual EXclusion object. + init() + /// Performs a critical piece of work synchronously after acquiring the MUTual + /// EXclusion object and releases MUTual EXclusion object when task completes. + /// + /// Use this to perform critical tasks or provide access to critical resource + /// that require exclusivity among other concurrent tasks. + /// + /// - Parameter critical: The critical task to perform. + /// - Returns: The result from the critical task. + /// - Throws: Error occurred running critical task. + @discardableResult + func perform(_ critical: () throws -> R) rethrows -> R +} diff --git a/Sources/AsyncObjects/Locker.swift b/Sources/AsyncObjects/Locks/Locker.swift similarity index 75% rename from Sources/AsyncObjects/Locker.swift rename to Sources/AsyncObjects/Locks/Locker.swift index 5445dfae..f5ab17b2 100644 --- a/Sources/AsyncObjects/Locker.swift +++ b/Sources/AsyncObjects/Locks/Locker.swift @@ -31,7 +31,8 @@ import Foundation /// } /// } /// ``` -public final class Locker: Equatable, Hashable, NSCopying, Sendable { +public final class Locker: Exclusible, Equatable, Hashable, NSCopying, Sendable +{ #if canImport(Darwin) /// A type representing data for an unfair lock. internal typealias Primitive = os_unfair_lock @@ -78,7 +79,6 @@ public final class Locker: Equatable, Hashable, NSCopying, Sendable { platformLock.deinitialize(count: 1) } - #if swift(>=5.7) /// Acquires exclusive lock. /// /// If a thread has already acquired lock and hasn't released lock yet, @@ -90,8 +90,7 @@ public final class Locker: Equatable, Hashable, NSCopying, Sendable { /// if called repeatedly from same thread without releasing /// with `_unlock()` beforehand. Use the ``perform(_:)`` /// method for safer handling of locking and unlocking. - @available(*, noasync, message: "use perform(_:) instead") - internal func _lock() { + private func _lock() { #if canImport(Darwin) os_unfair_lock_lock(platformLock) #elseif canImport(Glibc) @@ -114,8 +113,7 @@ public final class Locker: Equatable, Hashable, NSCopying, Sendable { /// if called from a thread calling `_lock()` beforehand. /// Use the ``perform(_:)`` method for safer handling /// of locking and unlocking. - @available(*, noasync, message: "use perform(_:) instead") - internal func _unlock() { + private func _unlock() { let threadDictionary = Thread.current.threadDictionary threadDictionary.removeObject(forKey: self) #if canImport(Darwin) @@ -126,53 +124,6 @@ public final class Locker: Equatable, Hashable, NSCopying, Sendable { ReleaseSRWLockExclusive(platformLock) #endif } - #else - /// Acquires exclusive lock. - /// - /// If a thread has already acquired lock and hasn't released lock yet, - /// other threads will wait for lock to be released and then acquire lock - /// in order of their request. - /// - /// - Warning: This method doesn't check if current thread - /// has already acquired lock, and will cause runtime error - /// if called repeatedly from same thread without releasing - /// with `_unlock()` beforehand. Use the ``perform(_:)`` - /// method for safer handling of locking and unlocking. - internal func _lock() { - #if canImport(Darwin) - os_unfair_lock_lock(platformLock) - #elseif canImport(Glibc) - pthread_mutex_lock(platformLock) - #elseif canImport(WinSDK) - AcquireSRWLockExclusive(platformLock) - #endif - // Track if thread is locked - let threadDictionary = Thread.current.threadDictionary - threadDictionary.setObject(true, forKey: self) - } - - /// Releases exclusive lock. - /// - /// A lock must be unlocked only from the same thread in which it was locked. - /// Attempting to unlock from a different thread causes a runtime error. - /// - /// - Warning: This method doesn't check if current thread - /// has already acquired lock, and will cause runtime error - /// if called from a thread calling `_lock()` beforehand. - /// Use the ``perform(_:)`` method for safer handling - /// of locking and unlocking. - internal func _unlock() { - let threadDictionary = Thread.current.threadDictionary - threadDictionary.removeObject(forKey: self) - #if canImport(Darwin) - os_unfair_lock_unlock(platformLock) - #elseif canImport(Glibc) - pthread_mutex_unlock(platformLock) - #elseif canImport(WinSDK) - ReleaseSRWLockExclusive(platformLock) - #endif - } - #endif /// Performs a critical piece of work synchronously after acquiring the lock /// and releases lock when task completes. diff --git a/Sources/AsyncObjects/TaskOperation.swift b/Sources/AsyncObjects/TaskOperation.swift index ce06dd0a..b692a100 100644 --- a/Sources/AsyncObjects/TaskOperation.swift +++ b/Sources/AsyncObjects/TaskOperation.swift @@ -1,8 +1,4 @@ -#if swift(>=5.7) import Foundation -#else -@preconcurrency import Foundation -#endif import Dispatch /// An object that bridges asynchronous work under structured concurrency @@ -33,7 +29,7 @@ import Dispatch /// operation.waitUntilFinished() /// ``` public final class TaskOperation: Operation, AsyncObject, - @unchecked Sendable + ContinuableCollection, @unchecked Sendable { /// The asynchronous action to perform as part of the operation.. private let underlyingAction: @Sendable () async throws -> R @@ -240,26 +236,6 @@ public final class TaskOperation: Operation, AsyncObject, locker.perform { continuations.removeValue(forKey: key) } } - /// Suspends the current task, then calls the given closure with a throwing continuation for the current task. - /// Continuation can be cancelled with error if current task is cancelled, by invoking `_removeContinuation`. - /// - /// Spins up a new continuation and requests to track it with key by invoking `_addContinuation`. - /// This operation cooperatively checks for cancellation and reacting to it by invoking `_removeContinuation`. - /// Continuation can be resumed with error and some cleanup code can be run here. - /// - /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. - @inlinable - internal func _withPromisedContinuation() async throws { - let key = UUID() - try await Continuation.withCancellation(synchronizedWith: locker) { - Task { [weak self] in self?._removeContinuation(withKey: key) } - } operation: { continuation in - Task { [weak self] in - self?._addContinuation(continuation, withKey: key) - } - } - } - /// Starts operation asynchronously /// as part of a new top-level task on behalf of the current actor. @Sendable diff --git a/Sources/AsyncObjects/TaskQueue.swift b/Sources/AsyncObjects/TaskQueue.swift index 33923f37..ef1c4cd4 100644 --- a/Sources/AsyncObjects/TaskQueue.swift +++ b/Sources/AsyncObjects/TaskQueue.swift @@ -7,7 +7,7 @@ import OrderedCollections /// An object that acts as a concurrent queue executing submitted tasks concurrently. /// -/// You can use the ``exec(priority:flags:operation:)-2ll3k`` +/// You can use the ``exec(priority:flags:operation:)-8u46i`` /// or its non-throwing/non-cancellable version to run tasks concurrently. /// Additionally, you can provide priority of task and ``Flags`` /// to customize execution of submitted operation. diff --git a/Tests/AsyncObjectsTests/NonThrowingFutureTests.swift b/Tests/AsyncObjectsTests/NonThrowingFutureTests.swift index 8af5134b..71326c7b 100644 --- a/Tests/AsyncObjectsTests/NonThrowingFutureTests.swift +++ b/Tests/AsyncObjectsTests/NonThrowingFutureTests.swift @@ -7,7 +7,7 @@ class NonThrowingFutureTests: XCTestCase { func testFutureFulfilledInitialization() async throws { let future = Future(with: .success(5)) - let value = await future.value + let value = await future.get() XCTAssertEqual(value, 5) } @@ -15,7 +15,7 @@ class NonThrowingFutureTests: XCTestCase { let future = Future() await withTaskGroup(of: Void.self) { group in group.addTask { - let value = await future.value + let value = await future.get() XCTAssertEqual(value, 5) } group.addTask { @@ -33,7 +33,7 @@ class NonThrowingFutureTests: XCTestCase { promise(.success(5)) } } - let value = await future.value + let value = await future.get() XCTAssertEqual(value, 5) } @@ -45,7 +45,7 @@ class NonThrowingFutureTests: XCTestCase { try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { - let value = await allFuture.value + let value = await allFuture.get() XCTAssertEqual(value, [1, 3, 2]) } // Make sure previous tasks started @@ -75,7 +75,7 @@ class NonThrowingFutureTests: XCTestCase { try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { - let values = await allFuture.value + let values = await allFuture.get() for (index, item) in values.enumerated() { switch item { case .success(let value): @@ -113,7 +113,7 @@ class NonThrowingFutureTests: XCTestCase { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { await Self.checkExecInterval(durationInSeconds: 1) { - let value = await allFuture.value + let value = await allFuture.get() XCTAssertEqual(value, 1) } } @@ -145,7 +145,7 @@ class NonThrowingFutureTests: XCTestCase { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { await Self.checkExecInterval(durationInSeconds: 1) { - let value = await allFuture.value + let value = await allFuture.get() XCTAssertEqual(value, 1) } } @@ -170,20 +170,20 @@ class NonThrowingFutureTests: XCTestCase { func testConstructingAllFutureFromZeroFutures() async { let future = Future.all() - let value = await future.value + let value = await future.get() XCTAssertTrue(value.isEmpty) } func testConstructingAllSettledFutureFromZeroFutures() async { let future = Future.allSettled() - let value = await future.value + let value = await future.get() XCTAssertTrue(value.isEmpty) } func testMultipleTimesFutureFulfilled() async throws { let future = Future(with: .success(5)) await future.fulfill(producing: 10) - let value = await future.value + let value = await future.get() XCTAssertEqual(value, 5) } @@ -202,7 +202,7 @@ class NonThrowingFutureTests: XCTestCase { try await Self.sleep(seconds: 1) await future.fulfill(producing: 5) } - let _ = await future.value + let _ = await future.get() self.addTeardownBlock { [weak future] in try await Self.sleep(seconds: 1) XCTAssertNil(future) @@ -216,7 +216,7 @@ class NonThrowingFutureTests: XCTestCase { let future = Future() await Self.checkExecInterval(durationInSeconds: 0) { await withTaskGroup(of: Void.self) { group in - group.addTask { let _ = await future.value } + group.addTask { let _ = await future.get() } group.addTask { await future.fulfill(producing: i) } await group.waitForAll() } diff --git a/Tests/AsyncObjectsTests/SafeContinuationTests.swift b/Tests/AsyncObjectsTests/SafeContinuationTests.swift index e08b622c..37b390c4 100644 --- a/Tests/AsyncObjectsTests/SafeContinuationTests.swift +++ b/Tests/AsyncObjectsTests/SafeContinuationTests.swift @@ -150,10 +150,9 @@ class SafeContinuationTests: XCTestCase { } catch {} XCTAssertTrue(Task.isCancelled) do { - try await SafeContinuation - .withCancellation { - } operation: { _ in - } + try await SafeContinuation.withCancellation { + } operation: { _ in + } XCTFail("Unexpected task progression") } catch { XCTAssertTrue(type(of: error) == CancellationError.self) @@ -163,4 +162,26 @@ class SafeContinuationTests: XCTestCase { task.cancel() await task.value } + + func testNonCancellableContinuation() async throws { + typealias C = GlobalContinuation + let task = Task.detached { + await Self.checkExecInterval(durationInSeconds: 1) { + do { + try await Self.sleep(seconds: 5) + XCTFail("Unexpected task progression") + } catch {} + XCTAssertTrue(Task.isCancelled) + await SafeContinuation.withCancellation { + } operation: { continuation in + Task { + defer { continuation.resume() } + try await Self.sleep(seconds: 1) + } + } + } + } + task.cancel() + await task.value + } } diff --git a/Tests/AsyncObjectsTests/StandardLibraryTests.swift b/Tests/AsyncObjectsTests/StandardLibraryTests.swift index b432082c..ebc14059 100644 --- a/Tests/AsyncObjectsTests/StandardLibraryTests.swift +++ b/Tests/AsyncObjectsTests/StandardLibraryTests.swift @@ -1,4 +1,5 @@ import XCTest +@testable import AsyncObjects /// Tests inner workings of structured concurrency class StandardLibraryTests: XCTestCase { diff --git a/Tests/AsyncObjectsTests/ThrowingFutureTests.swift b/Tests/AsyncObjectsTests/ThrowingFutureTests.swift index 8e8bdf7d..eb7b6579 100644 --- a/Tests/AsyncObjectsTests/ThrowingFutureTests.swift +++ b/Tests/AsyncObjectsTests/ThrowingFutureTests.swift @@ -7,7 +7,7 @@ class ThrowingFutureTests: XCTestCase { func testFutureFulfilledInitialization() async throws { let future = Future(with: .success(5)) - let value = try await future.value + let value = try await future.get() XCTAssertEqual(value, 5) } @@ -15,7 +15,7 @@ class ThrowingFutureTests: XCTestCase { let future = Future() try await withThrowingTaskGroup(of: Void.self) { group in group.addTask { - let value = try await future.value + let value = try await future.get() XCTAssertEqual(value, 5) } group.addTask { @@ -31,7 +31,7 @@ class ThrowingFutureTests: XCTestCase { try await withThrowingTaskGroup(of: Void.self) { group in group.addTask { do { - let _ = try await future.value + let _ = try await future.get() XCTFail("Unexpected task progression") } catch { XCTAssertTrue(type(of: error) == CancellationError.self) @@ -49,7 +49,7 @@ class ThrowingFutureTests: XCTestCase { let future = Future() let waitTask = Task { do { - let _ = try await future.value + let _ = try await future.get() XCTFail("Future fulfillments wait not cancelled") } catch { XCTAssertTrue(type(of: error) == CancellationError.self) @@ -76,7 +76,7 @@ class ThrowingFutureTests: XCTestCase { try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { - let value = try await allFuture.value + let value = try await allFuture.get() XCTAssertEqual(value, [1, 2, 3]) } // Make sure previous tasks started @@ -108,7 +108,7 @@ class ThrowingFutureTests: XCTestCase { await group.addTaskAndStart { await Self.checkExecInterval(durationInSeconds: 2) { do { - let _ = try await allFuture.value + let _ = try await allFuture.get() XCTFail("Future fulfillment did not fail") } catch { XCTAssertTrue( @@ -144,7 +144,7 @@ class ThrowingFutureTests: XCTestCase { try await Self.checkExecInterval(durationInSeconds: 3) { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { - let values = await allFuture.value + let values = await allFuture.get() for (index, item) in values.enumerated() { switch item { case .success(let value): @@ -182,7 +182,7 @@ class ThrowingFutureTests: XCTestCase { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { await Self.checkExecInterval(durationInSeconds: 3) { - let values = await allFuture.value + let values = await allFuture.get() for (index, item) in values.enumerated() { switch item { case .success(let value): @@ -224,7 +224,7 @@ class ThrowingFutureTests: XCTestCase { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { try await Self.checkExecInterval(durationInSeconds: 1) { - let value = try await allFuture.value + let value = try await allFuture.get() XCTAssertEqual(value, 1) } } @@ -257,7 +257,7 @@ class ThrowingFutureTests: XCTestCase { await group.addTaskAndStart { await Self.checkExecInterval(durationInSeconds: 1) { do { - let _ = try await allFuture.value + let _ = try await allFuture.get() XCTFail("Future fulfillment did not fail") } catch { XCTAssertTrue( @@ -294,7 +294,7 @@ class ThrowingFutureTests: XCTestCase { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { try await Self.checkExecInterval(durationInSeconds: 1) { - let value = try await allFuture.value + let value = try await allFuture.get() XCTAssertEqual(value, 1) } } @@ -326,7 +326,7 @@ class ThrowingFutureTests: XCTestCase { try await withThrowingTaskGroup(of: Void.self) { group in await group.addTaskAndStart { try await Self.checkExecInterval(durationInSeconds: 2) { - let value = try await allFuture.value + let value = try await allFuture.get() XCTAssertEqual(value, 2) } } @@ -359,7 +359,7 @@ class ThrowingFutureTests: XCTestCase { await group.addTaskAndStart { await Self.checkExecInterval(durationInSeconds: 3) { do { - let _ = try await allFuture.value + let _ = try await allFuture.get() XCTFail("Future fulfillment did not fail") } catch { XCTAssertTrue( @@ -399,20 +399,20 @@ class ThrowingFutureTests: XCTestCase { func testConstructingAllFutureFromZeroFutures() async throws { let future = Future.all() - let value = try await future.value + let value = try await future.get() XCTAssertTrue(value.isEmpty) } func testConstructingAllSettledFutureFromZeroFutures() async throws { let future = Future.allSettled() - let value = await future.value + let value = await future.get() XCTAssertTrue(value.isEmpty) } func testMultipleTimesFutureFulfilled() async throws { let future = Future(with: .success(5)) await future.fulfill(producing: 10) - let value = try await future.value + let value = try await future.get() XCTAssertEqual(value, 5) } @@ -422,7 +422,7 @@ class ThrowingFutureTests: XCTestCase { try await Self.sleep(seconds: 1) await future.fulfill(producing: 5) } - let _ = try await future.value + let _ = try await future.get() self.addTeardownBlock { [weak future] in try await Self.sleep(seconds: 1) XCTAssertNil(future) @@ -434,7 +434,7 @@ class ThrowingFutureTests: XCTestCase { let task = Task.detached { await Self.checkExecInterval(durationInSeconds: 0) { do { - let _ = try await future.value + let _ = try await future.get() XCTFail("Unexpected task progression") } catch { XCTAssertTrue(type(of: error) == CancellationError.self) @@ -455,7 +455,7 @@ class ThrowingFutureTests: XCTestCase { } catch {} XCTAssertTrue(Task.isCancelled) do { - let _ = try await future.value + let _ = try await future.get() XCTFail("Unexpected task progression") } catch { XCTAssertTrue(type(of: error) == CancellationError.self) @@ -474,7 +474,7 @@ class ThrowingFutureTests: XCTestCase { try await Self.checkExecInterval(durationInSeconds: 0) { try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { let _ = try await future.value } + group.addTask { let _ = try await future.get() } group.addTask { await future.fulfill(producing: i) } try await group.waitForAll() } From 62b2a63d13b64c4c5dc35e59d99a14cb06d09ef3 Mon Sep 17 00:00:00 2001 From: Soumya Ranjan Mahunt Date: Mon, 12 Sep 2022 16:59:50 +0530 Subject: [PATCH 06/14] refactor: remove unnecessary class from test --- .../AsyncSemaphoreTests.swift | 35 +++++++------------ 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/Tests/AsyncObjectsTests/AsyncSemaphoreTests.swift b/Tests/AsyncObjectsTests/AsyncSemaphoreTests.swift index 147f0ce5..53858c11 100644 --- a/Tests/AsyncObjectsTests/AsyncSemaphoreTests.swift +++ b/Tests/AsyncObjectsTests/AsyncSemaphoreTests.swift @@ -32,29 +32,31 @@ class AsyncSemaphoreTests: XCTestCase { durationInSeconds seconds: Int = 0 ) async throws { let semaphore = AsyncSemaphore(value: value) - let store = TaskTimeoutStore() try await Self.checkExecInterval(durationInSeconds: seconds) { - try await withThrowingTaskGroup(of: Void.self) { group in + try await withThrowingTaskGroup(of: Bool.self) { group in for _ in 0.. Date: Tue, 13 Sep 2022 17:42:51 +0530 Subject: [PATCH 07/14] refactor: refactor test helper functions --- .../AsyncCountdownEventTests.swift | 29 +++-- Tests/AsyncObjectsTests/AsyncEventTests.swift | 2 +- .../AsyncSemaphoreTests.swift | 2 +- .../CancellationSourceTests.swift | 28 ++--- .../NonThrowingFutureTests.swift | 8 +- Tests/AsyncObjectsTests/TaskQueueTests.swift | 48 ++++----- .../ThrowingFutureTests.swift | 18 ++-- Tests/AsyncObjectsTests/XCTestCase.swift | 101 ++++++------------ 8 files changed, 102 insertions(+), 134 deletions(-) diff --git a/Tests/AsyncObjectsTests/AsyncCountdownEventTests.swift b/Tests/AsyncObjectsTests/AsyncCountdownEventTests.swift index b3bb2958..ef48e8e0 100644 --- a/Tests/AsyncObjectsTests/AsyncCountdownEventTests.swift +++ b/Tests/AsyncObjectsTests/AsyncCountdownEventTests.swift @@ -26,8 +26,7 @@ class AsyncCountdownEventTests: XCTestCase { try await withThrowingTaskGroup(of: Void.self) { group in for i in 0..( name: String? = nil, - durationInSeconds seconds: Int = 0, + durationInSeconds seconds: T = .zero, for task: () async throws -> Void - ) async rethrows { - let time = DispatchTime.now() + ) async rethrows where T: Comparable { + let second: T = 1_000_000_000 + let time = DispatchTime.now().uptimeNanoseconds try await task() + guard + let span = T(exactly: DispatchTime.now().uptimeNanoseconds - time), + case let duration = span / second + else { XCTFail("Invalid number type: \(T.self)"); return } let assertions = { - XCTAssertEqual( - seconds, - Int( - (Double( - DispatchTime.now().uptimeNanoseconds - - time.uptimeNanoseconds - ) / 1E9).rounded(.toNearestOrAwayFromZero) - ) - ) - } - runAssertions(with: name, assertions) - } - - static func checkExecInterval( - name: String? = nil, - durationInSeconds seconds: Double = 0, - roundedUpTo digit: UInt = 1, - for task: () async throws -> Void - ) async rethrows { - let time = DispatchTime.now() - try await task() - let order = pow(10, Double(digit)) - let duration = - Double( - DispatchTime.now().uptimeNanoseconds - time.uptimeNanoseconds - ) * order - let assertions = { - XCTAssertEqual( - seconds, - (duration / 1E9).rounded() / order - ) + XCTAssertLessThanOrEqual(duration, seconds + 1) + XCTAssertGreaterThanOrEqual(duration, seconds - 1) } runAssertions(with: name, assertions) } @@ -71,14 +47,16 @@ extension XCTestCase { name: String? = nil, durationInRange range: R, for task: () async throws -> Void - ) async rethrows where R.Bound == Int { - let time = DispatchTime.now() + ) async rethrows where R.Bound: DivisiveArithmetic { + let second: R.Bound = 1_000_000_000 + let time = DispatchTime.now().uptimeNanoseconds try await task() - let duration = Int( - (Double( - DispatchTime.now().uptimeNanoseconds - time.uptimeNanoseconds - ) / 1E9).rounded(.toNearestOrAwayFromZero) - ) + guard + let span = R.Bound( + exactly: DispatchTime.now().uptimeNanoseconds - time + ), + case let duration = span / second + else { XCTFail("Invalid range type: \(R.self)"); return } let assertions = { XCTAssertTrue( range.contains(duration), @@ -88,32 +66,14 @@ extension XCTestCase { runAssertions(with: name, assertions) } - static func checkExecInterval( - name: String? = nil, - durationInRange range: R, - for task: () async throws -> Void - ) async rethrows where R.Bound == Double { - let time = DispatchTime.now() - try await task() - let duration = - Double( - DispatchTime.now().uptimeNanoseconds - time.uptimeNanoseconds - ) / 1E9 - let assertions = { - XCTAssertTrue( - range.contains(duration), - "\(duration) not present in \(range)" - ) - } - runAssertions(with: name, assertions) - } - - static func sleep(seconds: UInt64) async throws { - try await Task.sleep(nanoseconds: seconds * 1_000_000_000) + static func sleep(seconds: T) async throws { + let second: T = 1_000_000_000 + try await Task.sleep(nanoseconds: UInt64(exactly: seconds * second)!) } - static func sleep(forSeconds seconds: Double) async throws { - try await Task.sleep(nanoseconds: UInt64(seconds * 1E9)) + static func sleep(seconds: T) async throws { + let second: T = 1_000_000_000 + try await Task.sleep(nanoseconds: UInt64(exactly: seconds * second)!) } } @@ -124,3 +84,12 @@ extension AsyncObject { return try await self.wait(forNanoseconds: seconds * 1_000_000_000) } } + +protocol DivisiveArithmetic: Numeric { + static func / (lhs: Self, rhs: Self) -> Self + static func /= (lhs: inout Self, rhs: Self) +} + +extension Int: DivisiveArithmetic {} +extension Double: DivisiveArithmetic {} +extension UInt64: DivisiveArithmetic {} From 27ee8131b399acd4aa1508b33c3ac1cc271b9ac4 Mon Sep 17 00:00:00 2001 From: Soumya Ranjan Mahunt Date: Tue, 13 Sep 2022 19:13:26 +0530 Subject: [PATCH 08/14] fix: manage cancelling operation before starting properly --- .../AsyncObjects/AsyncCountdownEvent.swift | 4 --- Sources/AsyncObjects/AsyncEvent.swift | 4 --- Sources/AsyncObjects/AsyncSemaphore.swift | 4 --- .../Continuation/ContinuableCollection.swift | 4 --- Sources/AsyncObjects/Future.swift | 4 --- Sources/AsyncObjects/TaskOperation.swift | 27 ++++++++++++++++--- Sources/AsyncObjects/TaskQueue.swift | 4 --- .../TaskOperationTests.swift | 15 +++++++++++ 8 files changed, 39 insertions(+), 27 deletions(-) diff --git a/Sources/AsyncObjects/AsyncCountdownEvent.swift b/Sources/AsyncObjects/AsyncCountdownEvent.swift index c1445d08..601e4c83 100644 --- a/Sources/AsyncObjects/AsyncCountdownEvent.swift +++ b/Sources/AsyncObjects/AsyncCountdownEvent.swift @@ -1,8 +1,4 @@ -#if swift(>=5.7) -import Foundation -#else @preconcurrency import Foundation -#endif import OrderedCollections /// An event object that controls access to a resource between high and low priority tasks diff --git a/Sources/AsyncObjects/AsyncEvent.swift b/Sources/AsyncObjects/AsyncEvent.swift index 79e0577d..b74d401e 100644 --- a/Sources/AsyncObjects/AsyncEvent.swift +++ b/Sources/AsyncObjects/AsyncEvent.swift @@ -1,8 +1,4 @@ -#if swift(>=5.7) -import Foundation -#else @preconcurrency import Foundation -#endif /// An object that controls execution of tasks depending on the signal state. /// diff --git a/Sources/AsyncObjects/AsyncSemaphore.swift b/Sources/AsyncObjects/AsyncSemaphore.swift index 9a3072db..45ad911e 100644 --- a/Sources/AsyncObjects/AsyncSemaphore.swift +++ b/Sources/AsyncObjects/AsyncSemaphore.swift @@ -1,8 +1,4 @@ -#if swift(>=5.7) -import Foundation -#else @preconcurrency import Foundation -#endif import OrderedCollections /// An object that controls access to a resource across multiple task contexts through use of a traditional counting semaphore. diff --git a/Sources/AsyncObjects/Continuation/ContinuableCollection.swift b/Sources/AsyncObjects/Continuation/ContinuableCollection.swift index 4e18620e..f7dd2ab2 100644 --- a/Sources/AsyncObjects/Continuation/ContinuableCollection.swift +++ b/Sources/AsyncObjects/Continuation/ContinuableCollection.swift @@ -1,8 +1,4 @@ -#if swift(>=5.7) -import Foundation -#else @preconcurrency import Foundation -#endif /// A type that manages a collection of continuations with an associated key. /// diff --git a/Sources/AsyncObjects/Future.swift b/Sources/AsyncObjects/Future.swift index 80ac3c6d..ecf668e2 100644 --- a/Sources/AsyncObjects/Future.swift +++ b/Sources/AsyncObjects/Future.swift @@ -1,8 +1,4 @@ -#if swift(>=5.7) -import Foundation -#else @preconcurrency import Foundation -#endif /// An object that eventually produces a single value and then finishes or fails. /// diff --git a/Sources/AsyncObjects/TaskOperation.swift b/Sources/AsyncObjects/TaskOperation.swift index b692a100..0b0b17bb 100644 --- a/Sources/AsyncObjects/TaskOperation.swift +++ b/Sources/AsyncObjects/TaskOperation.swift @@ -62,12 +62,28 @@ public final class TaskOperation: Operation, AsyncObject, /// /// Always returns true, since the operation always executes its task asynchronously. public override var isAsynchronous: Bool { true } + + /// Private store for boolean value indicating whether the operation is currently cancelled. + @usableFromInline + internal var _isCancelled: Bool = false /// A Boolean value indicating whether the operation has been cancelled. /// /// Returns whether the underlying top-level task is cancelled or not. /// The default value of this property is `false`. /// Calling the ``cancel()`` method of this object sets the value of this property to `true`. - public override var isCancelled: Bool { execTask?.isCancelled ?? false } + public override internal(set) var isCancelled: Bool { + get { locker.perform { execTask?.isCancelled ?? _isCancelled } } + @usableFromInline + set { + willChangeValue(forKey: "isCancelled") + locker.perform { + _isCancelled = newValue + guard newValue else { return } + execTask?.cancel() + } + didChangeValue(forKey: "isCancelled") + } + } /// Private store for boolean value indicating whether the operation is currently executing. @usableFromInline @@ -113,7 +129,12 @@ public final class TaskOperation: Operation, AsyncObject, /// Will be success if provided operation completed successfully, /// or failure returned with error. public var result: Result { - get async { (await execTask?.result) ?? .failure(EarlyInvokeError()) } + get async { + (await execTask?.result) + ?? (isCancelled + ? .failure(CancellationError()) + : .failure(EarlyInvokeError())) + } } /// Creates a new operation that executes the provided asynchronous task. @@ -187,7 +208,7 @@ public final class TaskOperation: Operation, AsyncObject, /// Likewise, if the task has already run past the last point where it would stop early, /// calling this method has no effect. public override func cancel() { - execTask?.cancel() + isCancelled = true _finish() } diff --git a/Sources/AsyncObjects/TaskQueue.swift b/Sources/AsyncObjects/TaskQueue.swift index ef1c4cd4..d4acf7d9 100644 --- a/Sources/AsyncObjects/TaskQueue.swift +++ b/Sources/AsyncObjects/TaskQueue.swift @@ -1,8 +1,4 @@ -#if swift(>=5.7) -import Foundation -#else @preconcurrency import Foundation -#endif import OrderedCollections /// An object that acts as a concurrent queue executing submitted tasks concurrently. diff --git a/Tests/AsyncObjectsTests/TaskOperationTests.swift b/Tests/AsyncObjectsTests/TaskOperationTests.swift index 6d981c92..cccddda8 100644 --- a/Tests/AsyncObjectsTests/TaskOperationTests.swift +++ b/Tests/AsyncObjectsTests/TaskOperationTests.swift @@ -237,6 +237,21 @@ class TaskOperationTests: XCTestCase { } } + func testNotStartedCancellationError() async throws { + let operation = TaskOperation { try await Self.sleep(seconds: 1) } + operation.cancel() + let result = await operation.result + switch result { + case .success: XCTFail("Unexpected operation result") + case .failure(let error): + XCTAssertTrue(type(of: error) == CancellationError.self) + print( + "[\(#function)] [\(type(of: error))] \(error.localizedDescription)" + ) + XCTAssertFalse(error.localizedDescription.isEmpty) + } + } + func testWaitCancellationWhenTaskCancelled() async throws { let operation = TaskOperation { try await Self.sleep(seconds: 10) } let task = Task.detached { From 80b29f10dc247a58470a69dd59b7482b98738357 Mon Sep 17 00:00:00 2001 From: Soumya Ranjan Mahunt Date: Wed, 21 Sep 2022 02:18:13 +0530 Subject: [PATCH 09/14] deps: update swift-format with Swift 5.7 support --- Package.swift | 2 +- Sources/AsyncObjects/Continuation/Continuable.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Package.swift b/Package.swift index 9e2077cc..d70beb9a 100644 --- a/Package.swift +++ b/Package.swift @@ -21,7 +21,7 @@ let package = Package( dependencies: [ .package(url: "\(appleGitHub)/swift-collections.git", from: "1.0.0"), .package(url: "\(appleGitHub)/swift-docc-plugin", from: "1.0.0"), - .package(url: "\(appleGitHub)/swift-format", from: "0.50600.1"), + .package(url: "\(appleGitHub)/swift-format", from: "0.50700.0"), ], targets: [ .target( diff --git a/Sources/AsyncObjects/Continuation/Continuable.swift b/Sources/AsyncObjects/Continuation/Continuable.swift index 62bf675d..d8267812 100644 --- a/Sources/AsyncObjects/Continuation/Continuable.swift +++ b/Sources/AsyncObjects/Continuation/Continuable.swift @@ -5,7 +5,7 @@ /// Use continuations for interfacing Swift tasks with event loops, delegate methods, callbacks, /// and other non-async scheduling mechanisms. @rethrows -public protocol Continuable { +public protocol Continuable { /// The type of value to resume the continuation with in case of success. associatedtype Success /// The type of error to resume the continuation with in case of failure. @@ -170,7 +170,7 @@ public extension Continuable { /// Dummy cancellation method for continuations /// that don't support cancellation. @inlinable - func cancel() { /* Do nothing */ } + func cancel() { /* Do nothing */ } /// Resume the task awaiting the continuation by having it return normally from its suspension point. /// From fb559b60ea1ed89808c7004b61f5705b75a06f8d Mon Sep 17 00:00:00 2001 From: Soumya Ranjan Mahunt Date: Sat, 24 Sep 2022 18:28:45 +0530 Subject: [PATCH 10/14] wip: refactor safe continuation --- Sources/AsyncObjects/Continuation/SafeContinuation.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/AsyncObjects/Continuation/SafeContinuation.swift b/Sources/AsyncObjects/Continuation/SafeContinuation.swift index 375dcfc7..97794903 100644 --- a/Sources/AsyncObjects/Continuation/SafeContinuation.swift +++ b/Sources/AsyncObjects/Continuation/SafeContinuation.swift @@ -46,8 +46,9 @@ internal final class SafeContinuation: SynchronizedContinuable { /// - Returns: Whether the current status can be updated to provided status. private func validateStatus(_ status: Status) -> Bool { switch (self.status, status) { - case (.willResume, .waiting), (.willResume, .willResume): return false - case (.resumed, .waiting), (.resumed, .willResume): return false + case (.willResume, .waiting), (.willResume, .willResume), + (.resumed, .waiting), (.resumed, .willResume): + return false default: return true } } From 0dfea0768703fd8ca1afe58a016d2b2752e43ce6 Mon Sep 17 00:00:00 2001 From: Soumya Ranjan Mahunt Date: Tue, 27 Sep 2022 22:56:00 +0530 Subject: [PATCH 11/14] style: add logging parameters to public interfaces --- Package.swift | 8 +- .../AsyncObjects/AsyncCountdownEvent.swift | 81 +++- Sources/AsyncObjects/AsyncEvent.swift | 36 +- Sources/AsyncObjects/AsyncObject.swift | 172 ++++++- Sources/AsyncObjects/AsyncSemaphore.swift | 28 +- Sources/AsyncObjects/Future.swift | 429 +++++++++++++++--- Sources/AsyncObjects/TaskOperation.swift | 28 +- Sources/AsyncObjects/TaskQueue.swift | 74 ++- Tests/AsyncObjectsTests/AsyncEventTests.swift | 5 +- .../TaskOperationTests.swift | 5 +- 10 files changed, 754 insertions(+), 112 deletions(-) diff --git a/Package.swift b/Package.swift index d70beb9a..e98b3e41 100644 --- a/Package.swift +++ b/Package.swift @@ -42,7 +42,7 @@ let package = Package( ] ) -var swiftSettings: [SwiftSetting] { +var swiftSettings: [SwiftSetting] = { var swiftSettings: [SwiftSetting] = [] if ProcessInfo.processInfo.environment[ @@ -77,9 +77,9 @@ var swiftSettings: [SwiftSetting] { } return swiftSettings -} +}() -var testingSwiftSettings: [SwiftSetting] { +var testingSwiftSettings: [SwiftSetting] = { var swiftSettings: [SwiftSetting] = [] if ProcessInfo.processInfo.environment[ @@ -96,4 +96,4 @@ var testingSwiftSettings: [SwiftSetting] { } return swiftSettings -} +}() diff --git a/Sources/AsyncObjects/AsyncCountdownEvent.swift b/Sources/AsyncObjects/AsyncCountdownEvent.swift index 601e4c83..02617429 100644 --- a/Sources/AsyncObjects/AsyncCountdownEvent.swift +++ b/Sources/AsyncObjects/AsyncCountdownEvent.swift @@ -179,7 +179,12 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { /// Use this to indicate usage of resource from high priority tasks. /// /// - Parameter count: The value by which to increase ``currentCount``. - public nonisolated func increment(by count: UInt = 1) { + public nonisolated func increment( + by count: UInt = 1, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { Task { await _increment(by: count) } } @@ -187,7 +192,19 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { /// /// If the current count becomes less or equal to limit, multiple queued tasks /// are resumed from suspension until current count exceeds limit. - public nonisolated func reset() { + /// + /// - Parameters: + /// - file: The file reset originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function reset originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line reset originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + public nonisolated func reset( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { Task { await _reset() } } @@ -196,8 +213,20 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { /// If the current count becomes less or equal to limit, multiple queued tasks /// are resumed from suspension until current count exceeds limit. /// - /// - Parameter count: The new initial count. - public nonisolated func reset(to count: UInt) { + /// - Parameters: + /// - count: The new initial count. + /// - file: The file reset originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function reset originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line reset originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + public nonisolated func reset( + to count: UInt, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { Task { await _reset(to: count) } } @@ -205,7 +234,19 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { /// /// Decrement the countdown. If the current count becomes less or equal to limit, /// one queued task is resumed from suspension. - public nonisolated func signal() { + /// + /// - Parameters: + /// - file: The file signal originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function signal originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line signal originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + public nonisolated func signal( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { Task { await _decrementCount(by: 1) } } @@ -214,8 +255,20 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { /// Decrement the countdown by the provided count. If the current count becomes less or equal to limit, /// multiple queued tasks are resumed from suspension until current count exceeds limit. /// - /// - Parameter count: The number of signals to register. - public nonisolated func signal(repeat count: UInt) { + /// - Parameters: + /// - count: The number of signals to register. + /// - file: The file signal originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function signal originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line signal originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + public nonisolated func signal( + repeat count: UInt, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { Task { await _decrementCount(by: count) } } @@ -226,9 +279,21 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { /// /// Use this to wait for high priority tasks completion to start low priority ones. /// + /// - Parameters: + /// - file: The file wait request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function wait request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line wait request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// /// - Throws: `CancellationError` if cancelled. @Sendable - public func wait() async throws { + public func wait( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) async throws { guard _wait() else { currentCount += 1; return } try await _withPromisedContinuation() } diff --git a/Sources/AsyncObjects/AsyncEvent.swift b/Sources/AsyncObjects/AsyncEvent.swift index b74d401e..eab560a8 100644 --- a/Sources/AsyncObjects/AsyncEvent.swift +++ b/Sources/AsyncObjects/AsyncEvent.swift @@ -92,6 +92,14 @@ public actor AsyncEvent: AsyncObject, ContinuableCollection { /// Resets signal of event. /// /// After reset, tasks have to wait for event signal to complete. + /// + /// - Parameters: + /// - file: The file reset originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function reset originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line reset originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). @Sendable public nonisolated func reset() { Task { await _reset() } @@ -100,8 +108,20 @@ public actor AsyncEvent: AsyncObject, ContinuableCollection { /// Signals the event. /// /// Resumes all the tasks suspended and waiting for signal. + /// + /// - Parameters: + /// - file: The file signal originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function signal originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line signal originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). @Sendable - public nonisolated func signal() { + public nonisolated func signal( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { Task { await _signal() } } @@ -110,9 +130,21 @@ public actor AsyncEvent: AsyncObject, ContinuableCollection { /// Only waits asynchronously, if event is in non-signaled state, /// until event is signalled. /// + /// - Parameters: + /// - file: The file wait request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function wait request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line wait request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// /// - Throws: `CancellationError` if cancelled. @Sendable - public func wait() async throws { + public func wait( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) async throws { guard !signalled else { return } try await _withPromisedContinuation() } diff --git a/Sources/AsyncObjects/AsyncObject.swift b/Sources/AsyncObjects/AsyncObject.swift index d5cb3e30..b539de1a 100644 --- a/Sources/AsyncObjects/AsyncObject.swift +++ b/Sources/AsyncObjects/AsyncObject.swift @@ -9,8 +9,13 @@ public protocol AsyncObject: Sendable { /// /// Object might resume suspended tasks /// or synchronize tasks differently. + /// + /// - Parameters: + /// - file: The file signal originates from. + /// - function: The function signal originates from. + /// - line: The line signal originates from. @Sendable - func signal() + func signal(file: String, function: String, line: UInt) /// Waits for the object to green light task execution. /// /// Waits asynchronously suspending current task, instead of blocking any thread. @@ -18,9 +23,14 @@ public protocol AsyncObject: Sendable { /// /// Might throw some error or never throws depending on implementation. /// + /// - Parameters: + /// - file: The file wait request originates from. + /// - function: The function wait request originates from. + /// - line: The line signal wait request originates from. + /// /// - Note: Method might return immediately depending upon the synchronization object requirement. @Sendable - func wait() async throws + func wait(file: String, function: String, line: UInt) async throws } // TODO: add clock based timeout for Swift >=5.7 @@ -31,14 +41,27 @@ public extension AsyncObject { /// Depending upon whether wait succeeds or timeout expires result is returned. /// Async object has to resume the task at a later time depending on its requirement. /// - /// - Parameter duration: The duration in nano seconds to wait until. + /// - Parameters: + /// - duration: The duration in nano seconds to wait until. + /// - file: The file wait request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function wait request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line wait request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// /// - Throws: `CancellationError` if cancelled or `DurationTimeoutError` if timed out. /// - Note: Method might return immediately depending upon the synchronization object requirement. @Sendable - func wait(forNanoseconds duration: UInt64) async throws { + func wait( + forNanoseconds duration: UInt64, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) async throws { return try await waitForTaskCompletion( withTimeoutInNanoseconds: duration - ) { try await self.wait() } + ) { try await self.wait(file: file, function: function, line: line) } } } @@ -47,13 +70,30 @@ public extension AsyncObject { /// Invokes ``AsyncObject/wait()`` for all objects /// and returns only when all the invocation completes. /// -/// - Parameter objects: The objects to wait for. +/// - Parameters: +/// - objects: The objects to wait for. +/// - file: The file wait request originates from (there's usually no need to pass it +/// explicitly as it defaults to `#fileID`). +/// - function: The function wait request originates from (there's usually no need to +/// pass it explicitly as it defaults to `#function`). +/// - line: The line wait request originates from (there's usually no need to pass it +/// explicitly as it defaults to `#line`). +/// /// - Throws: `CancellationError` if cancelled. @inlinable @Sendable -public func waitForAll(_ objects: [any AsyncObject]) async throws { +public func waitForAll( + _ objects: [any AsyncObject], + file: String = #fileID, + function: String = #function, + line: UInt = #line +) async throws { try await withThrowingTaskGroup(of: Void.self) { group in - objects.forEach { group.addTask(operation: $0.wait) } + objects.forEach { obj in + group.addTask { + try await obj.wait(file: file, function: function, line: line) + } + } try await group.waitForAll() } } @@ -63,12 +103,25 @@ public func waitForAll(_ objects: [any AsyncObject]) async throws { /// Invokes ``AsyncObject/wait()`` for all objects /// and returns only when all the invocation completes. /// -/// - Parameter objects: The objects to wait for. +/// - Parameters: +/// - objects: The objects to wait for. +/// - file: The file wait request originates from (there's usually no need to pass it +/// explicitly as it defaults to `#fileID`). +/// - function: The function wait request originates from (there's usually no need to +/// pass it explicitly as it defaults to `#function`). +/// - line: The line wait request originates from (there's usually no need to pass it +/// explicitly as it defaults to `#line`). +/// /// - Throws: `CancellationError` if cancelled. @inlinable @Sendable -public func waitForAll(_ objects: any AsyncObject...) async throws { - try await waitForAll(objects) +public func waitForAll( + _ objects: any AsyncObject..., + file: String = #fileID, + function: String = #function, + line: UInt = #line +) async throws { + try await waitForAll(objects, file: file, function: function, line: line) } /// Waits for multiple objects to green light task execution @@ -81,6 +134,12 @@ public func waitForAll(_ objects: any AsyncObject...) async throws { /// - Parameters: /// - objects: The objects to wait for. /// - duration: The duration in nano seconds to wait until. +/// - file: The file wait request originates from (there's usually no need to pass it +/// explicitly as it defaults to `#fileID`). +/// - function: The function wait request originates from (there's usually no need to +/// pass it explicitly as it defaults to `#function`). +/// - line: The line wait request originates from (there's usually no need to pass it +/// explicitly as it defaults to `#line`). /// /// - Throws: `CancellationError` if cancelled /// or `DurationTimeoutError` if timed out. @@ -88,10 +147,16 @@ public func waitForAll(_ objects: any AsyncObject...) async throws { @Sendable public func waitForAll( _ objects: [any AsyncObject], - forNanoseconds duration: UInt64 + forNanoseconds duration: UInt64, + file: String = #fileID, + function: String = #function, + line: UInt = #line ) async throws { return try await waitForTaskCompletion(withTimeoutInNanoseconds: duration) { - try await waitForAll(objects) + try await waitForAll( + objects, + file: file, function: function, line: line + ) } } @@ -105,6 +170,12 @@ public func waitForAll( /// - Parameters: /// - objects: The objects to wait for. /// - duration: The duration in nano seconds to wait until. +/// - file: The file wait request originates from (there's usually no need to pass it +/// explicitly as it defaults to `#fileID`). +/// - function: The function wait request originates from (there's usually no need to +/// pass it explicitly as it defaults to `#function`). +/// - line: The line wait request originates from (there's usually no need to pass it +/// explicitly as it defaults to `#line`). /// /// - Throws: `CancellationError` if cancelled /// or `DurationTimeoutError` if timed out. @@ -112,9 +183,15 @@ public func waitForAll( @Sendable public func waitForAll( _ objects: any AsyncObject..., - forNanoseconds duration: UInt64 + forNanoseconds duration: UInt64, + file: String = #fileID, + function: String = #function, + line: UInt = #line ) async throws { - return try await waitForAll(objects, forNanoseconds: duration) + return try await waitForAll( + objects, forNanoseconds: duration, + file: file, function: function, line: line + ) } /// Waits for multiple objects to green light task execution @@ -126,16 +203,29 @@ public func waitForAll( /// - Parameters: /// - objects: The objects to wait for. /// - count: The number of objects to wait for. +/// - file: The file wait request originates from (there's usually no need to pass it +/// explicitly as it defaults to `#fileID`). +/// - function: The function wait request originates from (there's usually no need to +/// pass it explicitly as it defaults to `#function`). +/// - line: The line wait request originates from (there's usually no need to pass it +/// explicitly as it defaults to `#line`). /// /// - Throws: `CancellationError` if cancelled. @inlinable @Sendable public func waitForAny( _ objects: [any AsyncObject], - count: Int = 1 + count: Int = 1, + file: String = #fileID, + function: String = #function, + line: UInt = #line ) async throws { try await withThrowingTaskGroup(of: Void.self) { group in - objects.forEach { group.addTask(operation: $0.wait) } + objects.forEach { obj in + group.addTask { + try await obj.wait(file: file, function: function, line: line) + } + } for _ in 0.. { /// Creates a future that invokes a promise closure when the publisher emits an element. /// /// - Parameters: + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// - attemptToFulfill: A ``Future/Promise`` that the publisher invokes /// when the publisher emits an element or terminates with an error. /// /// - Returns: The newly created future. public init( + file: String = #fileID, + function: String = #function, + line: UInt = #line, attemptToFulfill: @Sendable @escaping ( @escaping Promise ) async -> Void @@ -107,7 +116,12 @@ public actor Future { Task { await attemptToFulfill { result in Task { [weak self] in - await self?.fulfill(with: result) + await self?.fulfill( + with: result, + file: file, + function: function, + line: line + ) } } } @@ -116,11 +130,20 @@ public actor Future { /// Creates a future that invokes a promise closure when the publisher emits an element. /// /// - Parameters: + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// - attemptToFulfill: A ``Future/Promise`` that the publisher invokes /// when the publisher emits an element or terminates with an error. /// /// - Returns: The newly created future. public convenience init( + file: String = #fileID, + function: String = #function, + line: UInt = #line, attemptToFulfill: @Sendable @escaping ( @escaping Promise ) async -> Void @@ -129,7 +152,12 @@ public actor Future { Task { await attemptToFulfill { result in Task { [weak self] in - await self?.fulfill(with: result) + await self?.fulfill( + with: result, + file: file, + function: function, + line: line + ) } } } @@ -147,9 +175,26 @@ public actor Future { /// A future must be fulfilled exactly once. If the future has already been fulfilled, /// then calling this method has no effect and returns immediately. /// - /// - Parameter value: The value to produce from the future. - public func fulfill(producing value: Output) { - self.fulfill(with: .success(value)) + /// - Parameters: + /// - value: The value to produce from the future. + /// - file: The file future fulfillment originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future fulfillment originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future fulfillment originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + public func fulfill( + producing value: Output, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + self.fulfill( + with: .success(value), + file: file, + function: function, + line: line + ) } /// Terminate the future with the given error and propagate error to subscribers. @@ -157,9 +202,26 @@ public actor Future { /// A future must be fulfilled exactly once. If the future has already been fulfilled, /// then calling this method has no effect and returns immediately. /// - /// - Parameter error: The error to throw to the callers. - public func fulfill(throwing error: Failure) { - self.fulfill(with: .failure(error)) + /// - Parameters: + /// - error: The error to throw to the callers. + /// - file: The file future fulfillment originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future fulfillment originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future fulfillment originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + public func fulfill( + throwing error: Failure, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + self.fulfill( + with: .failure(error), + file: file, + function: function, + line: line + ) } /// Fulfill the future by returning or throwing the given result value. @@ -167,10 +229,22 @@ public actor Future { /// A future must be fulfilled exactly once. If the future has already been fulfilled, /// then calling this method has no effect and returns immediately. /// - /// - Parameter result: The result. If it contains a `.success` value, - /// that value delivered asynchronously to callers; - /// otherwise, the awaiting caller receives the `.error` instead. - public func fulfill(with result: FutureResult) { + /// - Parameters: + /// - result: The result. If it contains a `.success` value, + /// that value delivered asynchronously to callers; + /// otherwise, the awaiting caller receives the `.error` instead. + /// - file: The file future fulfillment originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future fulfillment originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future fulfillment originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + public func fulfill( + with result: FutureResult, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { guard self.result == nil else { return } self.result = result continuations.forEach { $0.value.resume(with: result) } @@ -200,7 +274,19 @@ extension Future where Failure == Never { /// This property exposes the fulfilled value for the `Future` asynchronously. /// Immediately returns if `Future` is fulfilled otherwise waits asynchronously /// for `Future` to be fulfilled. - public func get() async -> Output { + /// + /// - Parameters: + /// - file: The file value request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function value request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line value request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + public func get( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) async -> Output { if let result = result { return try! result.get() } return await _withPromisedContinuation() } @@ -210,12 +296,22 @@ extension Future where Failure == Never { /// If the returned future fulfills, it is fulfilled with an aggregating array of the values from the fulfilled futures, /// in the same order as provided. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: Already fulfilled future if no future provided, or a pending future /// combining provided futures. public static func all( - _ futures: [Future] + _ futures: [Future], + file: String = #fileID, + function: String = #function, + line: UInt = #line ) -> Future<[Output], Failure> { typealias IndexedOutput = (index: Int, value: Output) guard !futures.isEmpty else { return .init(with: .success([])) } @@ -227,7 +323,11 @@ extension Future where Failure == Never { group.addTask { return ( index: index, - value: await future.get() + value: await future.get( + file: file, + function: function, + line: line + ) ) } } @@ -246,14 +346,24 @@ extension Future where Failure == Never { /// If the returned future fulfills, it is fulfilled with an aggregating array of the values from the fulfilled futures, /// in the same order as provided. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: Already fulfilled future if no future provided, or a pending future /// combining provided futures. public static func all( - _ futures: Future... + _ futures: Future..., + file: String = #fileID, + function: String = #function, + line: UInt = #line ) -> Future<[Output], Failure> { - return Self.all(futures) + return Self.all(futures, file: file, function: function, line: line) } /// Combines into a single future, for all futures to have settled. @@ -262,12 +372,22 @@ extension Future where Failure == Never { /// with an array of `Result`s that each describe the outcome of each future /// in the same order as provided. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: Already fulfilled future if no future provided, or a pending future /// combining provided futures. public static func allSettled( - _ futures: [Future] + _ futures: [Future], + file: String = #fileID, + function: String = #function, + line: UInt = #line ) -> Future<[FutureResult], Never> { typealias IndexedOutput = (index: Int, value: FutureResult) guard !futures.isEmpty else { return .init(with: .success([])) } @@ -279,7 +399,13 @@ extension Future where Failure == Never { group.addTask { return ( index: index, - value: .success(await future.get()) + value: .success( + await future.get( + file: file, + function: function, + line: line + ) + ) ) } } @@ -299,14 +425,29 @@ extension Future where Failure == Never { /// with an array of `Result`s that each describe the outcome of each future /// in the same order as provided. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: Already fulfilled future if no future provided, or a pending future /// combining provided futures. public static func allSettled( - _ futures: Future... + _ futures: Future..., + file: String = #fileID, + function: String = #function, + line: UInt = #line ) -> Future<[FutureResult], Never> { - return Self.allSettled(futures) + return Self.allSettled( + futures, + file: file, + function: function, + line: line + ) } /// Takes multiple futures and, returns a single future that fulfills with the value @@ -314,17 +455,33 @@ extension Future where Failure == Never { /// /// If the returned future fulfills, it is fulfilled with the value of the first future that fulfilled. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: A pending future combining provided futures, or a forever pending future /// if no future provided. public static func race( - _ futures: [Future] + _ futures: [Future], + file: String = #fileID, + function: String = #function, + line: UInt = #line ) -> Future { return .init { promise in await withTaskGroup(of: Output.self) { group in futures.forEach { future in - group.addTask { await future.get() } + group.addTask { + await future.get( + file: file, + function: function, + line: line + ) + } } if let first = await group.next() { promise(.success(first)) @@ -339,42 +496,72 @@ extension Future where Failure == Never { /// /// If the returned future fulfills, it is fulfilled with the value of the first future that fulfilled. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: A pending future combining provided futures, or a forever pending future /// if no future provided. public static func race( - _ futures: Future... + _ futures: Future..., + file: String = #fileID, + function: String = #function, + line: UInt = #line ) -> Future { - return Self.race(futures) + return Self.race(futures, file: file, function: function, line: line) } /// Takes multiple futures and, returns a single future that fulfills with the value as soon as one of the futures fulfills. /// /// If the returned future fulfills, it is fulfilled with the value of the first future that fulfilled. /// - /// - Parameter futures: The futures to wait for. + /// - Parameters: + /// - futures: The futures to wait for. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: A pending future waiting for first fulfilled future from provided futures, /// or a forever pending future if no future provided. public static func any( - _ futures: [Future] + _ futures: [Future], + file: String = #fileID, + function: String = #function, + line: UInt = #line ) -> Future { - return Self.race(futures) + return Self.race(futures, file: file, function: function, line: line) } /// Takes multiple futures and, returns a single future that fulfills with the value as soon as one of the futures fulfills. /// /// If the returned future fulfills, it is fulfilled with the value of the first future that fulfilled. /// - /// - Parameter futures: The futures to wait for. + /// - Parameters: + /// - futures: The futures to wait for. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: A pending future waiting for first fulfilled future from provided futures, /// or a forever pending future if no future provided. public static func any( - _ futures: Future... + _ futures: Future..., + file: String = #fileID, + function: String = #function, + line: UInt = #line ) -> Future { - return Self.any(futures) + return Self.any(futures, file: file, function: function, line: line) } } @@ -423,8 +610,20 @@ extension Future where Failure == Error { /// for `Future` to be fulfilled. If the Future terminates with an error, /// the awaiting caller receives the error instead. /// + /// - Parameters: + /// - file: The file value request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function value request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line value request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// /// - Throws: If future rejected with error or `CancellationError` if cancelled. - public func get() async throws -> Output { + public func get( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) async throws -> Output { if let result = result { return try result.get() } return try await _withPromisedContinuation() } @@ -436,12 +635,22 @@ extension Future where Failure == Error { /// /// If it rejects, it is rejected with the error from the first future that was rejected. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: Already fulfilled future if no future provided, or a pending future /// combining provided futures. public static func all( - _ futures: [Future] + _ futures: [Future], + file: String = #fileID, + function: String = #function, + line: UInt = #line ) -> Future<[Output], Failure> { typealias IndexedOutput = (index: Int, value: Output) guard !futures.isEmpty else { return .init(with: .success([])) } @@ -451,7 +660,14 @@ extension Future where Failure == Error { result.reserveCapacity(futures.count) for (index, future) in futures.enumerated() { group.addTask { - (index: index, value: try await future.get()) + ( + index: index, + value: try await future.get( + file: file, + function: function, + line: line + ) + ) } } do { @@ -476,14 +692,24 @@ extension Future where Failure == Error { /// /// If it rejects, it is rejected with the error from the first future that was rejected. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: Already fulfilled future if no future provided, or a pending future /// combining provided futures. public static func all( - _ futures: Future... + _ futures: Future..., + file: String = #fileID, + function: String = #function, + line: UInt = #line ) -> Future<[Output], Failure> { - return Self.all(futures) + return Self.all(futures, file: file, function: function, line: line) } /// Combines into a single future, for all futures to have settled (each may fulfill or reject). @@ -492,12 +718,22 @@ extension Future where Failure == Error { /// with an array of `Result`s that each describe the outcome of each future /// in the same order as provided. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: Already fulfilled future if no future provided, or a pending future /// combining provided futures. public static func allSettled( - _ futures: [Future] + _ futures: [Future], + file: String = #fileID, + function: String = #function, + line: UInt = #line ) -> Future<[FutureResult], Never> { typealias IndexedOutput = (index: Int, value: FutureResult) guard !futures.isEmpty else { return .init(with: .success([])) } @@ -508,7 +744,11 @@ extension Future where Failure == Error { for (index, future) in futures.enumerated() { group.addTask { do { - let value = try await future.get() + let value = try await future.get( + file: file, + function: function, + line: line + ) return (index: index, value: .success(value)) } catch { return (index: index, value: .failure(error)) @@ -531,14 +771,29 @@ extension Future where Failure == Error { /// with an array of `Result`s that each describe the outcome of each future /// in the same order as provided. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: Already fulfilled future if no future provided, or a pending future /// combining provided futures. public static func allSettled( - _ futures: Future... + _ futures: Future..., + file: String = #fileID, + function: String = #function, + line: UInt = #line ) -> Future<[FutureResult], Never> { - return Self.allSettled(futures) + return Self.allSettled( + futures, + file: file, + function: function, + line: line + ) } /// Takes multiple futures and, returns a single future that fulfills with the value @@ -548,17 +803,33 @@ extension Future where Failure == Error { /// /// If it rejects, it is rejected with the error from the first future that was rejected. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: A pending future combining provided futures, or a forever pending future /// if no future provided. public static func race( - _ futures: [Future] + _ futures: [Future], + file: String = #fileID, + function: String = #function, + line: UInt = #line ) -> Future { return .init { promise in await withThrowingTaskGroup(of: Output.self) { group in futures.forEach { future in - group.addTask { try await future.get() } + group.addTask { + try await future.get( + file: file, + function: function, + line: line + ) + } } do { if let first = try await group.next() { @@ -579,14 +850,24 @@ extension Future where Failure == Error { /// /// If it rejects, it is rejected with the error from the first future that was rejected. /// - /// - Parameter futures: The futures to combine. + /// - Parameters: + /// - futures: The futures to combine. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: A pending future combining provided futures, or a forever pending future /// if no future provided. public static func race( - _ futures: Future... + _ futures: Future..., + file: String = #fileID, + function: String = #function, + line: UInt = #line ) -> Future { - return Self.race(futures) + return Self.race(futures, file: file, function: function, line: line) } /// Takes multiple futures and, returns a single future that fulfills with the value as soon as one of the futures fulfills. @@ -595,12 +876,22 @@ extension Future where Failure == Error { /// /// If all the provided futures are rejected, it rejects with `CancellationError`. /// - /// - Parameter futures: The futures to wait for. + /// - Parameters: + /// - futures: The futures to wait for. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: A pending future waiting for first fulfilled future from provided futures, /// or a future rejected with `CancellationError` if no future provided. public static func any( - _ futures: [Future] + _ futures: [Future], + file: String = #fileID, + function: String = #function, + line: UInt = #line ) -> Future { guard !futures.isEmpty else { return .init(with: .cancelled) } return .init { promise in @@ -608,7 +899,11 @@ extension Future where Failure == Error { futures.forEach { future in group.addTask { do { - let value = try await future.get() + let value = try await future.get( + file: file, + function: function, + line: line + ) return .success(value) } catch { return .failure(error) @@ -642,14 +937,24 @@ extension Future where Failure == Error { /// /// If all the provided futures are rejected, it rejects with `CancellationError`. /// - /// - Parameter futures: The futures to wait for. + /// - Parameters: + /// - futures: The futures to wait for. + /// - file: The file future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function future initialization originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line future initialization originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: A pending future waiting for first fulfilled future from provided futures, /// or a future rejected with `CancellationError` if no future provided. public static func any( - _ futures: Future... + _ futures: Future..., + file: String = #fileID, + function: String = #function, + line: UInt = #line ) -> Future { - return Self.any(futures) + return Self.any(futures, file: file, function: function, line: line) } } diff --git a/Sources/AsyncObjects/TaskOperation.swift b/Sources/AsyncObjects/TaskOperation.swift index 0b0b17bb..deac5469 100644 --- a/Sources/AsyncObjects/TaskOperation.swift +++ b/Sources/AsyncObjects/TaskOperation.swift @@ -259,8 +259,20 @@ public final class TaskOperation: Operation, AsyncObject, /// Starts operation asynchronously /// as part of a new top-level task on behalf of the current actor. + /// + /// - Parameters: + /// - file: The file signal originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function signal originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line signal originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). @Sendable - public func signal() { + public func signal( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { self.start() } @@ -269,9 +281,21 @@ public final class TaskOperation: Operation, AsyncObject, /// Only waits asynchronously, if operation is executing, /// until it is completed or cancelled. /// + /// - Parameters: + /// - file: The file wait request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function wait request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line wait request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// /// - Throws: `CancellationError` if cancelled. @Sendable - public func wait() async throws { + public func wait( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) async throws { guard !isFinished else { return } try await _withPromisedContinuation() } diff --git a/Sources/AsyncObjects/TaskQueue.swift b/Sources/AsyncObjects/TaskQueue.swift index d4acf7d9..1c36ffd6 100644 --- a/Sources/AsyncObjects/TaskQueue.swift +++ b/Sources/AsyncObjects/TaskQueue.swift @@ -438,6 +438,12 @@ public actor TaskQueue: AsyncObject { /// from execution context(`Task.currentPriority`) for non-detached tasks. /// - flags: Additional attributes to apply when executing the operation. /// For a list of possible values, see ``Flags``. + /// - file: The file execution request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function execution request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line execution request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// - operation: The throwing operation to perform. /// /// - Returns: The result from provided operation. @@ -451,6 +457,9 @@ public actor TaskQueue: AsyncObject { public func exec( priority: TaskPriority? = nil, flags: Flags = [], + file: String = #fileID, + function: String = #function, + line: UInt = #line, operation: @Sendable @escaping () async throws -> T ) async throws -> T { return try await _execHelper( @@ -470,6 +479,12 @@ public actor TaskQueue: AsyncObject { /// from execution context(`Task.currentPriority`) for non-detached tasks. /// - flags: Additional attributes to apply when executing the operation. /// For a list of possible values, see ``Flags``. + /// - file: The file execution request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function execution request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line execution request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// - operation: The non-throwing operation to perform. /// /// - Returns: The result from provided operation. @@ -478,6 +493,9 @@ public actor TaskQueue: AsyncObject { public func exec( priority: TaskPriority? = nil, flags: Flags = [], + file: String = #fileID, + function: String = #function, + line: UInt = #line, operation: @Sendable @escaping () async -> T ) async -> T { return await _execHelper( @@ -500,17 +518,29 @@ public actor TaskQueue: AsyncObject { /// from execution context(`Task.currentPriority`) for non-detached tasks. /// - flags: Additional attributes to apply when executing the operation. /// For a list of possible values, see ``Flags``. + /// - file: The file execution request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function execution request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line execution request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// - operation: The throwing operation to perform. @Sendable public nonisolated func addTask( priority: TaskPriority? = nil, flags: Flags = [], + file: String = #fileID, + function: String = #function, + line: UInt = #line, operation: @Sendable @escaping () async throws -> T ) { Task { try await exec( priority: priority, flags: flags, + file: file, + function: function, + line: line, operation: operation ) } @@ -527,17 +557,29 @@ public actor TaskQueue: AsyncObject { /// from execution context(`Task.currentPriority`) for non-detached tasks. /// - flags: Additional attributes to apply when executing the operation. /// For a list of possible values, see ``Flags``. + /// - file: The file execution request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function execution request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line execution request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// - operation: The non-throwing operation to perform. @Sendable public nonisolated func addTask( priority: TaskPriority? = nil, flags: Flags = [], + file: String = #fileID, + function: String = #function, + line: UInt = #line, operation: @Sendable @escaping () async -> T ) { Task { await exec( priority: priority, flags: flags, + file: file, + function: function, + line: line, operation: operation ) } @@ -545,17 +587,43 @@ public actor TaskQueue: AsyncObject { /// Signalling on queue does nothing. /// Only added to satisfy ``AsyncObject`` requirements. + /// + /// - Parameters: + /// - file: The file signal originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function signal originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line signal originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). @Sendable - public nonisolated func signal() { /* Do nothing */ } + public nonisolated func signal( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { /* Do nothing */ } /// Waits for execution turn on queue. /// /// Only waits asynchronously, if queue is locked by a barrier task, /// until the suspended task's turn comes to be resumed. /// + /// - Parameters: + /// - file: The file wait request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function wait request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line wait request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// /// - Throws: `CancellationError` if cancelled. @Sendable - public func wait() async throws { - try await exec { try await Task.sleep(nanoseconds: 0) } + public func wait( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) async throws { + try await exec( + file: file, function: function, line: line + ) { try await Task.sleep(nanoseconds: 0) } } } diff --git a/Tests/AsyncObjectsTests/AsyncEventTests.swift b/Tests/AsyncObjectsTests/AsyncEventTests.swift index 87280585..a0cd31c7 100644 --- a/Tests/AsyncObjectsTests/AsyncEventTests.swift +++ b/Tests/AsyncObjectsTests/AsyncEventTests.swift @@ -14,9 +14,8 @@ class AsyncEventTests: XCTestCase { event.signal() } try await Self.checkExecInterval( - durationInSeconds: seconds, - for: event.wait - ) + durationInSeconds: seconds + ) { try await event.wait() } } func testEventWait() async throws { diff --git a/Tests/AsyncObjectsTests/TaskOperationTests.swift b/Tests/AsyncObjectsTests/TaskOperationTests.swift index cccddda8..097a94c7 100644 --- a/Tests/AsyncObjectsTests/TaskOperationTests.swift +++ b/Tests/AsyncObjectsTests/TaskOperationTests.swift @@ -134,9 +134,8 @@ class TaskOperationTests: XCTestCase { } operation.signal() try await Self.checkExecInterval( - durationInRange: ...3, - for: operation.wait - ) + durationInRange: ...3 + ) { try await operation.wait() } } func testTaskOperationAsyncWaitTimeout() async throws { From 3429a365e14ba5d65d73babde059245623fbf81d Mon Sep 17 00:00:00 2001 From: Soumya Ranjan Mahunt Date: Wed, 28 Sep 2022 10:05:35 +0530 Subject: [PATCH 12/14] style: add logging parameters to public interfaces --- .../AsyncObjects/AsyncCountdownEvent.swift | 2 +- Sources/AsyncObjects/AsyncEvent.swift | 4 +- Sources/AsyncObjects/AsyncSemaphore.swift | 2 +- Sources/AsyncObjects/CancellationSource.swift | 157 ++++++++++++++++-- Sources/AsyncObjects/Future.swift | 2 +- 5 files changed, 146 insertions(+), 21 deletions(-) diff --git a/Sources/AsyncObjects/AsyncCountdownEvent.swift b/Sources/AsyncObjects/AsyncCountdownEvent.swift index 02617429..c1efd461 100644 --- a/Sources/AsyncObjects/AsyncCountdownEvent.swift +++ b/Sources/AsyncObjects/AsyncCountdownEvent.swift @@ -220,7 +220,7 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { /// - function: The function reset originates from (there's usually no need to /// pass it explicitly as it defaults to `#function`). /// - line: The line reset originates from (there's usually no need to pass it - /// explicitly as it defaults to `#line`). + /// explicitly as it defaults to `#line`). public nonisolated func reset( to count: UInt, file: String = #fileID, diff --git a/Sources/AsyncObjects/AsyncEvent.swift b/Sources/AsyncObjects/AsyncEvent.swift index eab560a8..98e2b8a5 100644 --- a/Sources/AsyncObjects/AsyncEvent.swift +++ b/Sources/AsyncObjects/AsyncEvent.swift @@ -99,7 +99,7 @@ public actor AsyncEvent: AsyncObject, ContinuableCollection { /// - function: The function reset originates from (there's usually no need to /// pass it explicitly as it defaults to `#function`). /// - line: The line reset originates from (there's usually no need to pass it - /// explicitly as it defaults to `#line`). + /// explicitly as it defaults to `#line`). @Sendable public nonisolated func reset() { Task { await _reset() } @@ -115,7 +115,7 @@ public actor AsyncEvent: AsyncObject, ContinuableCollection { /// - function: The function signal originates from (there's usually no need to /// pass it explicitly as it defaults to `#function`). /// - line: The line signal originates from (there's usually no need to pass it - /// explicitly as it defaults to `#line`). + /// explicitly as it defaults to `#line`). @Sendable public nonisolated func signal( file: String = #fileID, diff --git a/Sources/AsyncObjects/AsyncSemaphore.swift b/Sources/AsyncObjects/AsyncSemaphore.swift index 5ffc5078..389acfca 100644 --- a/Sources/AsyncObjects/AsyncSemaphore.swift +++ b/Sources/AsyncObjects/AsyncSemaphore.swift @@ -140,7 +140,7 @@ public actor AsyncSemaphore: AsyncObject, ContinuableCollection { /// - function: The function wait request originates from (there's usually no need to /// pass it explicitly as it defaults to `#function`). /// - line: The line wait request originates from (there's usually no need to pass it - /// explicitly as it defaults to `#line`). + /// explicitly as it defaults to `#line`). /// /// - Throws: `CancellationError` if cancelled. @Sendable diff --git a/Sources/AsyncObjects/CancellationSource.swift b/Sources/AsyncObjects/CancellationSource.swift index e3edd102..40c1c3be 100644 --- a/Sources/AsyncObjects/CancellationSource.swift +++ b/Sources/AsyncObjects/CancellationSource.swift @@ -75,7 +75,7 @@ public actor CancellationSource { internal nonisolated func _propagateCancellation() async { await withTaskGroup(of: Void.self) { group in let linkedSources = await linkedSources - linkedSources.forEach { group.addTask(operation: $0.cancel) } + linkedSources.forEach { s in group.addTask{ s.cancel() } } await group.waitForAll() } } @@ -131,13 +131,30 @@ public actor CancellationSource { /// Creates a new cancellation source object /// and triggers cancellation event on this object after specified timeout. /// - /// - Parameter nanoseconds: The delay after which cancellation event triggered. + /// - Parameters: + /// - nanoseconds: The delay after which cancellation event triggered. + /// - file: The file cancel request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function cancel request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line cancel request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: The newly created cancellation source. - public init(cancelAfterNanoseconds nanoseconds: UInt64) { + public init( + cancelAfterNanoseconds nanoseconds: UInt64, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { self.init() Task { [weak self] in - try await self?.cancel(afterNanoseconds: nanoseconds) + try await self?.cancel( + afterNanoseconds: nanoseconds, + file: file, + function: function, + line: line + ) } } #else @@ -176,13 +193,30 @@ public actor CancellationSource { /// Creates a new cancellation source object /// and triggers cancellation event on this object after specified timeout. /// - /// - Parameter nanoseconds: The delay after which cancellation event triggered. + /// - Parameters: + /// - nanoseconds: The delay after which cancellation event triggered. + /// - file: The file cancel request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function cancel request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line cancel request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// /// - Returns: The newly created cancellation source. - public convenience init(cancelAfterNanoseconds nanoseconds: UInt64) { + public convenience init( + cancelAfterNanoseconds nanoseconds: UInt64, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { self.init() Task { [weak self] in - try await self?.cancel(afterNanoseconds: nanoseconds) + try await self?.cancel( + afterNanoseconds: nanoseconds, + file: file, + function: function, + line: line + ) } } #endif @@ -191,10 +225,20 @@ public actor CancellationSource { /// /// If task completes before cancellation event is triggered, it is automatically unregistered. /// - /// - Parameter task: The task to register. + /// - Parameters: + /// - task: The task to register. + /// - file: The file task registration originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function task registration originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line task registration originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). @Sendable public nonisolated func register( - task: Task + task: Task, + file: String = #fileID, + function: String = #function, + line: UInt = #line ) { Task { [weak self] in await self?._add(task: task) @@ -205,8 +249,20 @@ public actor CancellationSource { /// Trigger cancellation event, initiate cooperative cancellation of registered tasks /// and propagate cancellation to linked cancellation sources. + /// + /// - Parameters: + /// - file: The file cancel request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function cancel request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line cancel request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). @Sendable - public nonisolated func cancel() { + public nonisolated func cancel( + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { Task { await _cancel() } } @@ -214,10 +270,23 @@ public actor CancellationSource { /// initiate cooperative cancellation of registered tasks /// and propagate cancellation to linked cancellation sources. /// - /// - Parameter nanoseconds: The delay after which cancellation event triggered. + /// - Parameters: + /// - nanoseconds: The delay after which cancellation event triggered. + /// - file: The file cancel request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function cancel request originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line cancel request originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). + /// /// - Throws: `CancellationError` if cancelled. @Sendable - public func cancel(afterNanoseconds nanoseconds: UInt64) async throws { + public func cancel( + afterNanoseconds nanoseconds: UInt64, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) async throws { try await Task.sleep(nanoseconds: nanoseconds) await _cancel() } @@ -232,6 +301,12 @@ public extension Task { /// - Parameters: /// - priority: The priority of the task. Pass `nil` to use the priority from `Task.currentPriority`. /// - cancellationSource: The cancellation source on which new task will be registered for cancellation. + /// - file: The file task registration originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function task registration originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line task registration originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// - operation: The operation to perform. /// /// - Returns: The newly created task. @@ -239,10 +314,18 @@ public extension Task { init( priority: TaskPriority? = nil, cancellationSource: CancellationSource, + file: String = #fileID, + function: String = #function, + line: UInt = #line, operation: @escaping @Sendable () async -> Success ) where Failure == Never { self.init(priority: priority, operation: operation) - cancellationSource.register(task: self) + cancellationSource.register( + task: self, + file: file, + function: function, + line: line + ) } /// Runs the given throwing operation asynchronously as part of a new top-level task on behalf of the current actor, @@ -253,6 +336,12 @@ public extension Task { /// - Parameters: /// - priority: The priority of the task. Pass `nil` to use the priority from `Task.currentPriority`. /// - cancellationSource: The cancellation source on which new task will be registered for cancellation. + /// - file: The file task registration originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function task registration originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line task registration originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// - operation: The operation to perform. /// /// - Returns: The newly created task. @@ -260,10 +349,18 @@ public extension Task { init( priority: TaskPriority? = nil, cancellationSource: CancellationSource, + file: String = #fileID, + function: String = #function, + line: UInt = #line, operation: @escaping @Sendable () async throws -> Success ) where Failure == Error { self.init(priority: priority, operation: operation) - cancellationSource.register(task: self) + cancellationSource.register( + task: self, + file: file, + function: function, + line: line + ) } /// Runs the given non-throwing operation asynchronously as part of a new top-level task, @@ -274,6 +371,12 @@ public extension Task { /// - Parameters: /// - priority: The priority of the task. /// - cancellationSource: The cancellation source on which new task will be registered for cancellation. + /// - file: The file task registration originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function task registration originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line task registration originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// - operation: The operation to perform. /// /// - Returns: The newly created task. @@ -281,10 +384,18 @@ public extension Task { static func detached( priority: TaskPriority? = nil, cancellationSource: CancellationSource, + file: String = #fileID, + function: String = #function, + line: UInt = #line, operation: @escaping @Sendable () async -> Success ) -> Self where Failure == Never { let task = Task.detached(priority: priority, operation: operation) - cancellationSource.register(task: task) + cancellationSource.register( + task: task, + file: file, + function: function, + line: line + ) return task } @@ -296,6 +407,12 @@ public extension Task { /// - Parameters: /// - priority: The priority of the task. /// - cancellationSource: The cancellation source on which new task will be registered for cancellation. + /// - file: The file task registration originates from (there's usually no need to pass it + /// explicitly as it defaults to `#fileID`). + /// - function: The function task registration originates from (there's usually no need to + /// pass it explicitly as it defaults to `#function`). + /// - line: The line task registration originates from (there's usually no need to pass it + /// explicitly as it defaults to `#line`). /// - operation: The operation to perform. /// /// - Returns: The newly created task. @@ -303,10 +420,18 @@ public extension Task { static func detached( priority: TaskPriority? = nil, cancellationSource: CancellationSource, + file: String = #fileID, + function: String = #function, + line: UInt = #line, operation: @escaping @Sendable () async throws -> Success ) -> Self where Failure == Error { let task = Task.detached(priority: priority, operation: operation) - cancellationSource.register(task: task) + cancellationSource.register( + task: task, + file: file, + function: function, + line: line + ) return task } } diff --git a/Sources/AsyncObjects/Future.swift b/Sources/AsyncObjects/Future.swift index 567f0f16..4f16848d 100644 --- a/Sources/AsyncObjects/Future.swift +++ b/Sources/AsyncObjects/Future.swift @@ -281,7 +281,7 @@ extension Future where Failure == Never { /// - function: The function value request originates from (there's usually no need to /// pass it explicitly as it defaults to `#function`). /// - line: The line value request originates from (there's usually no need to pass it - /// explicitly as it defaults to `#line`). + /// explicitly as it defaults to `#line`). public func get( file: String = #fileID, function: String = #function, From 18a87871a857f5bf08f99eb44b65d270a79376a3 Mon Sep 17 00:00:00 2001 From: Soumya Ranjan Mahunt Date: Fri, 11 Nov 2022 22:14:03 +0530 Subject: [PATCH 13/14] wip: remove `@preconcurrency` from `Foundation` for Swift >=5.7 --- Sources/AsyncObjects/AsyncCountdownEvent.swift | 5 +++++ Sources/AsyncObjects/AsyncEvent.swift | 4 ++++ Sources/AsyncObjects/AsyncSemaphore.swift | 5 +++++ .../AsyncObjects/Continuation/ContinuableCollection.swift | 4 ++++ Sources/AsyncObjects/Future.swift | 4 ++++ Sources/AsyncObjects/TaskQueue.swift | 5 +++++ Tests/AsyncObjectsTests/StandardLibraryTests.swift | 4 ++-- 7 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Sources/AsyncObjects/AsyncCountdownEvent.swift b/Sources/AsyncObjects/AsyncCountdownEvent.swift index c1efd461..2c7e70e7 100644 --- a/Sources/AsyncObjects/AsyncCountdownEvent.swift +++ b/Sources/AsyncObjects/AsyncCountdownEvent.swift @@ -1,4 +1,9 @@ +#if swift(>=5.7) +import Foundation +#else @preconcurrency import Foundation +#endif + import OrderedCollections /// An event object that controls access to a resource between high and low priority tasks diff --git a/Sources/AsyncObjects/AsyncEvent.swift b/Sources/AsyncObjects/AsyncEvent.swift index 98e2b8a5..6235767e 100644 --- a/Sources/AsyncObjects/AsyncEvent.swift +++ b/Sources/AsyncObjects/AsyncEvent.swift @@ -1,4 +1,8 @@ +#if swift(>=5.7) +import Foundation +#else @preconcurrency import Foundation +#endif /// An object that controls execution of tasks depending on the signal state. /// diff --git a/Sources/AsyncObjects/AsyncSemaphore.swift b/Sources/AsyncObjects/AsyncSemaphore.swift index 389acfca..aed424f7 100644 --- a/Sources/AsyncObjects/AsyncSemaphore.swift +++ b/Sources/AsyncObjects/AsyncSemaphore.swift @@ -1,4 +1,9 @@ +#if swift(>=5.7) +import Foundation +#else @preconcurrency import Foundation +#endif + import OrderedCollections /// An object that controls access to a resource across multiple task contexts through use of a traditional counting semaphore. diff --git a/Sources/AsyncObjects/Continuation/ContinuableCollection.swift b/Sources/AsyncObjects/Continuation/ContinuableCollection.swift index f7dd2ab2..4e18620e 100644 --- a/Sources/AsyncObjects/Continuation/ContinuableCollection.swift +++ b/Sources/AsyncObjects/Continuation/ContinuableCollection.swift @@ -1,4 +1,8 @@ +#if swift(>=5.7) +import Foundation +#else @preconcurrency import Foundation +#endif /// A type that manages a collection of continuations with an associated key. /// diff --git a/Sources/AsyncObjects/Future.swift b/Sources/AsyncObjects/Future.swift index 4f16848d..2d99aeef 100644 --- a/Sources/AsyncObjects/Future.swift +++ b/Sources/AsyncObjects/Future.swift @@ -1,4 +1,8 @@ +#if swift(>=5.7) +import Foundation +#else @preconcurrency import Foundation +#endif /// An object that eventually produces a single value and then finishes or fails. /// diff --git a/Sources/AsyncObjects/TaskQueue.swift b/Sources/AsyncObjects/TaskQueue.swift index 1c36ffd6..ea9cbe8a 100644 --- a/Sources/AsyncObjects/TaskQueue.swift +++ b/Sources/AsyncObjects/TaskQueue.swift @@ -1,4 +1,9 @@ +#if swift(>=5.7) +import Foundation +#else @preconcurrency import Foundation +#endif + import OrderedCollections /// An object that acts as a concurrent queue executing submitted tasks concurrently. diff --git a/Tests/AsyncObjectsTests/StandardLibraryTests.swift b/Tests/AsyncObjectsTests/StandardLibraryTests.swift index ebc14059..159cbfea 100644 --- a/Tests/AsyncObjectsTests/StandardLibraryTests.swift +++ b/Tests/AsyncObjectsTests/StandardLibraryTests.swift @@ -48,7 +48,7 @@ class StandardLibraryTests: XCTestCase { } @TaskLocal - nonisolated static var traceID: Int = 0 + static var traceID: Int = 0 func testTaskLocalVariable() async { func call(_ value: Int) { XCTAssertEqual(Self.traceID, value) @@ -122,7 +122,7 @@ class StandardLibraryTests: XCTestCase { } @TaskLocal - nonisolated static var localRef: TaskLocalClass! + static var localRef: TaskLocalClass! func testTaskLocalVariableWithReferenceType() { @Sendable func call(label: String, fromFunction function: String = #function) { From a9cb9bf2fa6f8a9d70af16862bd0cb31bbd99899 Mon Sep 17 00:00:00 2001 From: Soumya Ranjan Mahunt Date: Sat, 12 Nov 2022 12:26:59 +0530 Subject: [PATCH 14/14] wip: standardize method names --- .../AsyncObjects/AsyncCountdownEvent.swift | 42 ++++++------- Sources/AsyncObjects/AsyncEvent.swift | 14 ++--- Sources/AsyncObjects/AsyncSemaphore.swift | 16 ++--- Sources/AsyncObjects/CancellationSource.swift | 26 ++++---- .../Continuation/ContinuableCollection.swift | 20 +++---- Sources/AsyncObjects/Future.swift | 26 ++++---- Sources/AsyncObjects/Locks/Locker.swift | 12 ++-- Sources/AsyncObjects/TaskOperation.swift | 12 ++-- Sources/AsyncObjects/TaskQueue.swift | 60 +++++++++---------- 9 files changed, 114 insertions(+), 114 deletions(-) diff --git a/Sources/AsyncObjects/AsyncCountdownEvent.swift b/Sources/AsyncObjects/AsyncCountdownEvent.swift index 2c7e70e7..813292a7 100644 --- a/Sources/AsyncObjects/AsyncCountdownEvent.swift +++ b/Sources/AsyncObjects/AsyncCountdownEvent.swift @@ -77,13 +77,13 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { /// /// - Returns: Whether to wait to be resumed later. @inlinable - internal func _wait() -> Bool { !isSet || !continuations.isEmpty } + internal func shouldWait() -> Bool { !isSet || !continuations.isEmpty } /// Resume provided continuation with additional changes based on the associated flags. /// /// - Parameter continuation: The queued continuation to resume. @inlinable - internal func _resumeContinuation(_ continuation: Continuation) { + internal func resumeContinuation(_ continuation: Continuation) { currentCount += 1 continuation.resume() } @@ -94,12 +94,12 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { /// - continuation: The `continuation` to add. /// - key: The key in the map. @inlinable - internal func _addContinuation( + internal func addContinuation( _ continuation: Continuation, withKey key: UUID ) { guard !continuation.resumed else { return } - guard _wait() else { _resumeContinuation(continuation); return } + guard shouldWait() else { resumeContinuation(continuation); return } continuations[key] = continuation } @@ -108,7 +108,7 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { /// /// - Parameter key: The key in the map. @inlinable - internal func _removeContinuation(withKey key: UUID) { + internal func removeContinuation(withKey key: UUID) { continuations.removeValue(forKey: key) } @@ -116,18 +116,18 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { /// /// - Parameter number: The number to decrement count by. @inlinable - internal func _decrementCount(by number: UInt = 1) { - defer { _resumeContinuations() } + internal func decrementCount(by number: UInt = 1) { + defer { resumeContinuations() } guard currentCount > 0 else { return } currentCount -= number } /// Resume previously waiting continuations for countdown event. @inlinable - internal func _resumeContinuations() { + internal func resumeContinuations() { while !continuations.isEmpty && isSet { let (_, continuation) = continuations.removeFirst() - _resumeContinuation(continuation) + resumeContinuation(continuation) } } @@ -135,25 +135,25 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { /// /// - Parameter count: The value by which to increase ``currentCount``. @inlinable - internal func _increment(by count: UInt = 1) { + internal func incrementCount(by count: UInt = 1) { self.currentCount += count } /// Resets current count to initial count. @inlinable - internal func _reset() { + internal func resetCount() { self.currentCount = initialCount - _resumeContinuations() + resumeContinuations() } /// Resets initial count and current count to specified value. /// /// - Parameter count: The new initial count. @inlinable - internal func _reset(to count: UInt) { + internal func resetCount(to count: UInt) { initialCount = count self.currentCount = count - _resumeContinuations() + resumeContinuations() } // MARK: Public @@ -190,7 +190,7 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { function: String = #function, line: UInt = #line ) { - Task { await _increment(by: count) } + Task { await incrementCount(by: count) } } /// Resets current count to initial count. @@ -210,7 +210,7 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { function: String = #function, line: UInt = #line ) { - Task { await _reset() } + Task { await resetCount() } } /// Resets initial count and current count to specified value. @@ -232,7 +232,7 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { function: String = #function, line: UInt = #line ) { - Task { await _reset(to: count) } + Task { await resetCount(to: count) } } /// Registers a signal (decrements) with the countdown event. @@ -252,7 +252,7 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { function: String = #function, line: UInt = #line ) { - Task { await _decrementCount(by: 1) } + Task { await decrementCount(by: 1) } } /// Registers multiple signals (decrements by provided count) with the countdown event. @@ -274,7 +274,7 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { function: String = #function, line: UInt = #line ) { - Task { await _decrementCount(by: count) } + Task { await decrementCount(by: count) } } /// Waits for, or increments, a countdown event. @@ -299,7 +299,7 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollection { function: String = #function, line: UInt = #line ) async throws { - guard _wait() else { currentCount += 1; return } - try await _withPromisedContinuation() + guard shouldWait() else { currentCount += 1; return } + try await withPromisedContinuation() } } diff --git a/Sources/AsyncObjects/AsyncEvent.swift b/Sources/AsyncObjects/AsyncEvent.swift index 6235767e..85fe77ac 100644 --- a/Sources/AsyncObjects/AsyncEvent.swift +++ b/Sources/AsyncObjects/AsyncEvent.swift @@ -47,7 +47,7 @@ public actor AsyncEvent: AsyncObject, ContinuableCollection { /// - continuation: The `continuation` to add. /// - key: The key in the map. @inlinable - internal func _addContinuation( + internal func addContinuation( _ continuation: Continuation, withKey key: UUID ) { @@ -61,20 +61,20 @@ public actor AsyncEvent: AsyncObject, ContinuableCollection { /// /// - Parameter key: The key in the map. @inlinable - internal func _removeContinuation(withKey key: UUID) { + internal func removeContinuation(withKey key: UUID) { continuations.removeValue(forKey: key) } /// Resets signal of event. @inlinable - internal func _reset() { + internal func resetEvent() { signalled = false } /// Signals the event and resumes all the tasks /// suspended and waiting for signal. @inlinable - internal func _signal() { + internal func signalEvent() { continuations.forEach { $0.value.resume() } continuations = [:] signalled = true @@ -106,7 +106,7 @@ public actor AsyncEvent: AsyncObject, ContinuableCollection { /// explicitly as it defaults to `#line`). @Sendable public nonisolated func reset() { - Task { await _reset() } + Task { await resetEvent() } } /// Signals the event. @@ -126,7 +126,7 @@ public actor AsyncEvent: AsyncObject, ContinuableCollection { function: String = #function, line: UInt = #line ) { - Task { await _signal() } + Task { await signalEvent() } } /// Waits for event signal, or proceeds if already signalled. @@ -150,6 +150,6 @@ public actor AsyncEvent: AsyncObject, ContinuableCollection { line: UInt = #line ) async throws { guard !signalled else { return } - try await _withPromisedContinuation() + try await withPromisedContinuation() } } diff --git a/Sources/AsyncObjects/AsyncSemaphore.swift b/Sources/AsyncObjects/AsyncSemaphore.swift index aed424f7..a1a6c4d9 100644 --- a/Sources/AsyncObjects/AsyncSemaphore.swift +++ b/Sources/AsyncObjects/AsyncSemaphore.swift @@ -59,7 +59,7 @@ public actor AsyncSemaphore: AsyncObject, ContinuableCollection { /// - continuation: The `continuation` to add. /// - key: The key in the map. @inlinable - internal func _addContinuation( + internal func addContinuation( _ continuation: Continuation, withKey key: UUID ) { @@ -74,22 +74,22 @@ public actor AsyncSemaphore: AsyncObject, ContinuableCollection { /// /// - Parameter key: The key in the map. @inlinable - internal func _removeContinuation(withKey key: UUID) { + internal func removeContinuation(withKey key: UUID) { continuations.removeValue(forKey: key) - _incrementCount() + incrementCount() } /// Increments semaphore count within limit provided. @inlinable - internal func _incrementCount() { + internal func incrementCount() { guard count < limit else { return } count += 1 } /// Signals (increments) and releases a semaphore. @inlinable - internal func _signal() { - _incrementCount() + internal func signalSemaphore() { + incrementCount() guard !continuations.isEmpty else { return } let (_, continuation) = continuations.removeFirst() continuation.resume() @@ -131,7 +131,7 @@ public actor AsyncSemaphore: AsyncObject, ContinuableCollection { function: String = #function, line: UInt = #line ) { - Task { await _signal() } + Task { await signalSemaphore() } } /// Waits for, or decrements, a semaphore. @@ -155,6 +155,6 @@ public actor AsyncSemaphore: AsyncObject, ContinuableCollection { line: UInt = #line ) async throws { guard count <= 1 else { count -= 1; return } - try await _withPromisedContinuation() + try await withPromisedContinuation() } } diff --git a/Sources/AsyncObjects/CancellationSource.swift b/Sources/AsyncObjects/CancellationSource.swift index 40c1c3be..b174a5c6 100644 --- a/Sources/AsyncObjects/CancellationSource.swift +++ b/Sources/AsyncObjects/CancellationSource.swift @@ -49,7 +49,7 @@ public actor CancellationSource { /// /// - Parameter task: The task to register. @inlinable - internal func _add(task: Task) { + internal func add(task: Task) { guard !task.isCancelled else { return } registeredTasks[task] = { task.cancel() } } @@ -58,7 +58,7 @@ public actor CancellationSource { /// /// - Parameter task: The task to remove. @inlinable - internal func _remove(task: Task) { + internal func remove(task: Task) { registeredTasks.removeValue(forKey: task) } @@ -66,26 +66,26 @@ public actor CancellationSource { /// /// - Parameter task: The source to link. @inlinable - internal func _addSource(_ source: CancellationSource) { + internal func addSource(_ source: CancellationSource) { linkedSources.append(source) } /// Propagate cancellation to linked cancellation sources. @inlinable - internal nonisolated func _propagateCancellation() async { + internal nonisolated func propagateCancellation() async { await withTaskGroup(of: Void.self) { group in let linkedSources = await linkedSources - linkedSources.forEach { s in group.addTask{ s.cancel() } } + linkedSources.forEach { s in group.addTask { s.cancel() } } await group.waitForAll() } } /// Trigger cancellation event, initiate cooperative cancellation of registered tasks /// and propagate cancellation to linked cancellation sources. - internal func _cancel() async { + internal func cancelAll() async { registeredTasks.forEach { $1() } registeredTasks = [:] - await _propagateCancellation() + await propagateCancellation() } // MARK: Public @@ -109,7 +109,7 @@ public actor CancellationSource { Task { await withTaskGroup(of: Void.self) { group in sources.forEach { source in - group.addTask { await source._addSource(self) } + group.addTask { await source.addSource(self) } } await group.waitForAll() } @@ -171,7 +171,7 @@ public actor CancellationSource { Task { await withTaskGroup(of: Void.self) { group in sources.forEach { source in - group.addTask { await source._addSource(self) } + group.addTask { await source.addSource(self) } } await group.waitForAll() } @@ -241,9 +241,9 @@ public actor CancellationSource { line: UInt = #line ) { Task { [weak self] in - await self?._add(task: task) + await self?.add(task: task) let _ = await task.result - await self?._remove(task: task) + await self?.remove(task: task) } } @@ -263,7 +263,7 @@ public actor CancellationSource { function: String = #function, line: UInt = #line ) { - Task { await _cancel() } + Task { await cancelAll() } } /// Trigger cancellation event after provided delay, @@ -288,7 +288,7 @@ public actor CancellationSource { line: UInt = #line ) async throws { try await Task.sleep(nanoseconds: nanoseconds) - await _cancel() + await cancelAll() } } diff --git a/Sources/AsyncObjects/Continuation/ContinuableCollection.swift b/Sources/AsyncObjects/Continuation/ContinuableCollection.swift index 4e18620e..38fb669e 100644 --- a/Sources/AsyncObjects/Continuation/ContinuableCollection.swift +++ b/Sources/AsyncObjects/Continuation/ContinuableCollection.swift @@ -25,16 +25,16 @@ internal protocol ContinuableCollection { /// - Parameters: /// - continuation: The continuation value to add /// - key: The key to associate continuation with. - func _addContinuation(_ continuation: Continuation, withKey key: Key) async + func addContinuation(_ continuation: Continuation, withKey key: Key) async /// Remove continuation with the associated key from collection out of tracking. /// /// - Parameter key: The key for continuation to remove. - func _removeContinuation(withKey key: Key) async + func removeContinuation(withKey key: Key) async /// Suspends the current task, then calls the given closure with a continuation for the current task. /// /// - Returns: The value continuation is resumed with. /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. - func _withPromisedContinuation() async rethrows -> Continuation.Success + func withPromisedContinuation() async rethrows -> Continuation.Success } extension ContinuableCollection { @@ -43,7 +43,7 @@ extension ContinuableCollection { /// Default implementation that does nothing. /// /// - Parameter key: The key for continuation to remove. - func _removeContinuation(withKey key: Key) async { /* Do nothing */ } + func removeContinuation(withKey key: Key) async { /* Do nothing */ } } extension ContinuableCollection @@ -53,26 +53,26 @@ where Continuation.Lock == Lock, Key == UUID { /// Suspends the current task, then calls the given closure with a throwing continuation for the current task. - /// Continuation can be cancelled with error if current task is cancelled, by invoking `_removeContinuation`. + /// Continuation can be cancelled with error if current task is cancelled, by invoking `removeContinuation`. /// - /// Spins up a new continuation and requests to track it with key by invoking `_addContinuation`. - /// This operation cooperatively checks for cancellation and reacting to it by invoking `_removeContinuation`. + /// Spins up a new continuation and requests to track it with key by invoking `addContinuation`. + /// This operation cooperatively checks for cancellation and reacting to it by invoking `removeContinuation`. /// Continuation can be resumed with error and some cleanup code can be run here. /// /// - Returns: The value continuation is resumed with. /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. @inlinable - func _withPromisedContinuation() async rethrows -> Continuation.Success { + func withPromisedContinuation() async rethrows -> Continuation.Success { let key = UUID() return try await Continuation.withCancellation( synchronizedWith: locker ) { Task { [weak self] in - await self?._removeContinuation(withKey: key) + await self?.removeContinuation(withKey: key) } } operation: { continuation in Task { [weak self] in - await self?._addContinuation(continuation, withKey: key) + await self?.addContinuation(continuation, withKey: key) } } } diff --git a/Sources/AsyncObjects/Future.swift b/Sources/AsyncObjects/Future.swift index 2d99aeef..f8bb626c 100644 --- a/Sources/AsyncObjects/Future.swift +++ b/Sources/AsyncObjects/Future.swift @@ -70,7 +70,7 @@ public actor Future { /// - continuation: The `continuation` to add. /// - key: The key in the map. @inlinable - internal func _addContinuation( + internal func addContinuation( _ continuation: Continuation, withKey key: UUID = .init() ) { @@ -260,15 +260,15 @@ public actor Future { extension Future where Failure == Never { /// Suspends the current task, then calls the given closure with a non-throwing continuation for the current task. /// - /// Spins up a new continuation and requests to track it with key by invoking `_addContinuation`. + /// Spins up a new continuation and requests to track it with key by invoking `addContinuation`. /// This operation doesn't check for cancellation. /// /// - Returns: The value continuation is resumed with. @inlinable - internal nonisolated func _withPromisedContinuation() async -> Output { + internal nonisolated func withPromisedContinuation() async -> Output { return await Continuation.with { continuation in Task { [weak self] in - await self?._addContinuation(continuation) + await self?.addContinuation(continuation) } } } @@ -292,7 +292,7 @@ extension Future where Failure == Never { line: UInt = #line ) async -> Output { if let result = result { return try! result.get() } - return await _withPromisedContinuation() + return await withPromisedContinuation() } /// Combines into a single future, for all futures to be fulfilled. @@ -576,33 +576,33 @@ extension Future where Failure == Error { /// /// - Parameter key: The key in the map. @inlinable - internal func _removeContinuation(withKey key: UUID) { + internal func removeContinuation(withKey key: UUID) { continuations.removeValue(forKey: key) } /// Suspends the current task, then calls the given closure with a throwing continuation for the current task. - /// Continuation can be cancelled with error if current task is cancelled, by invoking `_removeContinuation`. + /// Continuation can be cancelled with error if current task is cancelled, by invoking `removeContinuation`. /// - /// Spins up a new continuation and requests to track it with key by invoking `_addContinuation`. - /// This operation cooperatively checks for cancellation and reacting to it by invoking `_removeContinuation`. + /// Spins up a new continuation and requests to track it with key by invoking `addContinuation`. + /// This operation cooperatively checks for cancellation and reacting to it by invoking `removeContinuation`. /// Continuation can be resumed with error and some cleanup code can be run here. /// /// - Returns: The value continuation is resumed with. /// /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. @inlinable - internal nonisolated func _withPromisedContinuation() async throws -> Output + internal nonisolated func withPromisedContinuation() async throws -> Output { let key = UUID() return try await Continuation.withCancellation( synchronizedWith: locker ) { Task { [weak self] in - await self?._removeContinuation(withKey: key) + await self?.removeContinuation(withKey: key) } } operation: { continuation in Task { [weak self] in - await self?._addContinuation(continuation, withKey: key) + await self?.addContinuation(continuation, withKey: key) } } } @@ -629,7 +629,7 @@ extension Future where Failure == Error { line: UInt = #line ) async throws -> Output { if let result = result { return try result.get() } - return try await _withPromisedContinuation() + return try await withPromisedContinuation() } /// Combines into a single future, for all futures to be fulfilled, or for any to be rejected. diff --git a/Sources/AsyncObjects/Locks/Locker.swift b/Sources/AsyncObjects/Locks/Locker.swift index f5ab17b2..3267f33f 100644 --- a/Sources/AsyncObjects/Locks/Locker.swift +++ b/Sources/AsyncObjects/Locks/Locker.swift @@ -88,9 +88,9 @@ public final class Locker: Exclusible, Equatable, Hashable, NSCopying, Sendable /// - Warning: This method doesn't check if current thread /// has already acquired lock, and will cause runtime error /// if called repeatedly from same thread without releasing - /// with `_unlock()` beforehand. Use the ``perform(_:)`` + /// with `unlock()` beforehand. Use the ``perform(_:)`` /// method for safer handling of locking and unlocking. - private func _lock() { + private func lock() { #if canImport(Darwin) os_unfair_lock_lock(platformLock) #elseif canImport(Glibc) @@ -110,10 +110,10 @@ public final class Locker: Exclusible, Equatable, Hashable, NSCopying, Sendable /// /// - Warning: This method doesn't check if current thread /// has already acquired lock, and will cause runtime error - /// if called from a thread calling `_lock()` beforehand. + /// if called from a thread calling `lock()` beforehand. /// Use the ``perform(_:)`` method for safer handling /// of locking and unlocking. - private func _unlock() { + private func unlock() { let threadDictionary = Thread.current.threadDictionary threadDictionary.removeObject(forKey: self) #if canImport(Darwin) @@ -145,8 +145,8 @@ public final class Locker: Exclusible, Equatable, Hashable, NSCopying, Sendable !(threadDictionary[self] as? Bool ?? false) else { return try critical() } - _lock() - defer { _unlock() } + lock() + defer { unlock() } return try critical() } diff --git a/Sources/AsyncObjects/TaskOperation.swift b/Sources/AsyncObjects/TaskOperation.swift index deac5469..46acb6c2 100644 --- a/Sources/AsyncObjects/TaskOperation.swift +++ b/Sources/AsyncObjects/TaskOperation.swift @@ -191,7 +191,7 @@ public final class TaskOperation: Operation, AsyncObject, /// as part of a new top-level task on behalf of the current actor. public override func main() { guard isExecuting, execTask == nil else { return } - let final = { @Sendable[weak self] in self?._finish(); return } + let final = { @Sendable[weak self] in self?.finish(); return } execTask = flags.createTask( priority: priority, operation: underlyingAction, @@ -209,14 +209,14 @@ public final class TaskOperation: Operation, AsyncObject, /// calling this method has no effect. public override func cancel() { isCancelled = true - _finish() + finish() } /// Moves this operation to finished state. /// /// Must be called either when operation completes or cancelled. @inlinable - internal func _finish() { + internal func finish() { isExecuting = false isFinished = true } @@ -237,7 +237,7 @@ public final class TaskOperation: Operation, AsyncObject, /// - continuation: The `continuation` to add. /// - key: The key in the map. @inlinable - internal func _addContinuation( + internal func addContinuation( _ continuation: Continuation, withKey key: UUID ) { @@ -253,7 +253,7 @@ public final class TaskOperation: Operation, AsyncObject, /// /// - Parameter key: The key in the map. @inlinable - internal func _removeContinuation(withKey key: UUID) { + internal func removeContinuation(withKey key: UUID) { locker.perform { continuations.removeValue(forKey: key) } } @@ -297,7 +297,7 @@ public final class TaskOperation: Operation, AsyncObject, line: UInt = #line ) async throws { guard !isFinished else { return } - try await _withPromisedContinuation() + try await withPromisedContinuation() } } diff --git a/Sources/AsyncObjects/TaskQueue.swift b/Sources/AsyncObjects/TaskQueue.swift index ea9cbe8a..4dece155 100644 --- a/Sources/AsyncObjects/TaskQueue.swift +++ b/Sources/AsyncObjects/TaskQueue.swift @@ -190,7 +190,7 @@ public actor TaskQueue: AsyncObject { /// - Parameter flags: The flags provided for new task. /// - Returns: Whether to wait to be resumed later. @inlinable - internal func _wait(whenFlags flags: Flags) -> Bool { + internal func shouldWait(whenFlags flags: Flags) -> Bool { return blocked || !queue.isEmpty || flags.wait(forCurrent: currentRunning) @@ -202,7 +202,7 @@ public actor TaskQueue: AsyncObject { /// - Returns: Whether queue is free to proceed scheduling other tasks. @inlinable @discardableResult - internal func _resumeQueuedContinuation( + internal func resumeQueuedContinuation( _ continuation: QueuedContinuation ) -> Bool { currentRunning += 1 @@ -218,13 +218,13 @@ public actor TaskQueue: AsyncObject { /// - key: The key in the continuation queue. /// - continuation: The continuation and flags to add to queue. @inlinable - internal func _queueContinuation( + internal func queueContinuation( atKey key: UUID = .init(), _ continuation: QueuedContinuation ) { guard !continuation.value.resumed else { return } - guard _wait(whenFlags: continuation.flags) else { - _resumeQueuedContinuation(continuation) + guard shouldWait(whenFlags: continuation.flags) else { + resumeQueuedContinuation(continuation) return } queue[key] = continuation @@ -234,7 +234,7 @@ public actor TaskQueue: AsyncObject { /// /// - Parameter key: The key in the continuation queue. @inlinable - internal func _dequeueContinuation(withKey key: UUID) { + internal func dequeueContinuation(withKey key: UUID) { queue.removeValue(forKey: key) } @@ -244,9 +244,9 @@ public actor TaskQueue: AsyncObject { /// Updates the ``blocked`` flag and starts queued tasks /// in order of their addition if any tasks are queued. @inlinable - internal func _unblockQueue() { + internal func unblockQueue() { blocked = false - _resumeQueuedTasks() + resumeQueuedTasks() } /// Signals completion of operation to the queue @@ -255,8 +255,8 @@ public actor TaskQueue: AsyncObject { /// Updates the ``currentRunning`` count and starts /// queued tasks in order of their addition if any queued. @inlinable - internal func _signalCompletion() { - defer { _resumeQueuedTasks() } + internal func signalCompletion() { + defer { resumeQueuedTasks() } guard currentRunning > 0 else { return } currentRunning -= 1 } @@ -264,28 +264,28 @@ public actor TaskQueue: AsyncObject { /// Resumes queued tasks when queue isn't blocked /// and operation flags preconditions satisfied. @inlinable - internal func _resumeQueuedTasks() { + internal func resumeQueuedTasks() { while let (_, continuation) = queue.elements.first, !blocked, !continuation.flags.wait(forCurrent: currentRunning) { queue.removeFirst() - guard _resumeQueuedContinuation(continuation) else { break } + guard resumeQueuedContinuation(continuation) else { break } } } /// Suspends the current task, then calls the given closure with a throwing continuation for the current task. - /// Continuation can be cancelled with error if current task is cancelled, by invoking `_dequeueContinuation`. + /// Continuation can be cancelled with error if current task is cancelled, by invoking `dequeueContinuation`. /// - /// Spins up a new continuation and requests to track it on queue with key by invoking `_queueContinuation`. - /// This operation cooperatively checks for cancellation and reacting to it by invoking `_dequeueContinuation`. + /// Spins up a new continuation and requests to track it on queue with key by invoking `queueContinuation`. + /// This operation cooperatively checks for cancellation and reacting to it by invoking `dequeueContinuation`. /// Continuation can be resumed with error and some cleanup code can be run here. /// /// - Parameter flags: The flags associated that determine the execution behavior of task. /// /// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error. @inlinable - internal nonisolated func _withPromisedContinuation( + internal nonisolated func withPromisedContinuation( flags: Flags = [] ) async throws { let key = UUID() @@ -293,11 +293,11 @@ public actor TaskQueue: AsyncObject { synchronizedWith: locker ) { Task { [weak self] in - await self?._dequeueContinuation(withKey: key) + await self?.dequeueContinuation(withKey: key) } } operation: { continuation in Task { [weak self] in - await self?._queueContinuation( + await self?.queueContinuation( atKey: key, (value: continuation, flags: flags) ) @@ -315,12 +315,12 @@ public actor TaskQueue: AsyncObject { /// - Returns: The result from provided operation. /// - Throws: `CancellationError` if cancelled, or error from provided operation. @inlinable - internal func _run( + internal func run( with priority: TaskPriority?, flags: Flags, operation: @Sendable @escaping () async throws -> T ) async rethrows -> T { - defer { _signalCompletion() } + defer { signalCompletion() } typealias LocalTask = Task let taskPriority = flags.choosePriority( fromContext: self.priority, @@ -349,14 +349,14 @@ public actor TaskQueue: AsyncObject { /// - Returns: The result from provided operation. /// - Throws: `CancellationError` if cancelled, or error from provided operation. @inlinable - internal func _runBlocking( + internal func runBlocking( with priority: TaskPriority?, flags: Flags, operation: @Sendable @escaping () async throws -> T ) async rethrows -> T { - defer { _unblockQueue() } + defer { unblockQueue() } blocked = true - return try await _run( + return try await run( with: priority, flags: flags, operation: operation @@ -397,7 +397,7 @@ public actor TaskQueue: AsyncObject { /// - Throws: Error from provided operation or the cancellation handler. @discardableResult @inlinable - internal func _execHelper( + internal func execHelper( priority: TaskPriority? = nil, flags: Flags = [], operation: @Sendable @escaping () async throws -> T, @@ -407,25 +407,25 @@ public actor TaskQueue: AsyncObject { _ operation: @Sendable @escaping () async throws -> T ) async rethrows -> T { return flags.isBlockEnabled - ? try await _runBlocking( + ? try await runBlocking( with: priority, flags: flags, operation: operation ) - : try await _run( + : try await run( with: priority, flags: flags, operation: operation ) } - guard self._wait(whenFlags: flags) else { + guard self.shouldWait(whenFlags: flags) else { currentRunning += 1 return try await runTask(operation) } do { - try await _withPromisedContinuation(flags: flags) + try await withPromisedContinuation(flags: flags) } catch { try cancellation(error) } @@ -467,7 +467,7 @@ public actor TaskQueue: AsyncObject { line: UInt = #line, operation: @Sendable @escaping () async throws -> T ) async throws -> T { - return try await _execHelper( + return try await execHelper( priority: priority, flags: flags, operation: operation @@ -503,7 +503,7 @@ public actor TaskQueue: AsyncObject { line: UInt = #line, operation: @Sendable @escaping () async -> T ) async -> T { - return await _execHelper( + return await execHelper( priority: priority, flags: flags, operation: operation