Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
854 changes: 429 additions & 425 deletions AsyncObjects.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Sources/AsyncObjects/AsyncCountdownEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,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, ContinuableCollection,
public actor AsyncCountdownEvent: AsyncObject, ContinuableCollectionActor,
LoggableActor
{
/// The suspended tasks continuation type.
Expand Down
3 changes: 2 additions & 1 deletion Sources/AsyncObjects/AsyncEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ import Foundation
/// // signal event after completing some task
/// event.signal()
/// ```
public actor AsyncEvent: AsyncObject, ContinuableCollection, LoggableActor {
public actor AsyncEvent: AsyncObject, ContinuableCollectionActor, LoggableActor
{
/// The suspended tasks continuation type.
@usableFromInline
internal typealias Continuation = TrackedContinuation<
Expand Down
6 changes: 4 additions & 2 deletions Sources/AsyncObjects/AsyncSemaphore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ import OrderedCollections
/// // release after executing critical async tasks
/// defer { semaphore.signal() }
/// ```
public actor AsyncSemaphore: AsyncObject, ContinuableCollection, LoggableActor {
public actor AsyncSemaphore: AsyncObject, ContinuableCollectionActor,
LoggableActor
{
/// The suspended tasks continuation type.
@usableFromInline
internal typealias Continuation = TrackedContinuation<
Expand Down Expand Up @@ -113,8 +115,8 @@ public actor AsyncSemaphore: AsyncObject, ContinuableCollection, LoggableActor {
withKey key: UUID,
file: String, function: String, line: UInt
) {
log("Removing", id: key, file: file, function: function, line: line)
incrementCount()
log("Removing", id: key, file: file, function: function, line: line)
continuations.removeValue(forKey: key)
guard !continuation.resumed else {
log(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Foundation

/// A type that manages a collection of continuations with an associated key.
///
/// While removing continuation, the continuation should be cancelled
/// While removing continuation, the continuation should be cancelled.
@rethrows
internal protocol ContinuableCollection {
/// The continuation item type in collection.
Expand Down
118 changes: 118 additions & 0 deletions Sources/AsyncObjects/Continuation/ContinuableCollectionActor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#if swift(>=5.7)
import Foundation

/// An actor type that manages a collection of continuations with an associated key.
///
/// On `Swift 5.7` and above [actor isolation bug with protocol conformance](https://forums.swift.org/t/actor-isolation-is-broken-by-protocol-conformance/57040)
/// is fixed, and hence original protocol can be used without any issue.
typealias ContinuableCollectionActor = ContinuableCollection
#else
@preconcurrency import Foundation

/// An actor type that manages a collection of continuations with an associated key.
///
/// This is to avoid [actor isolation bug with protocol conformance on older `Swift` versions](https://forums.swift.org/t/actor-isolation-is-broken-by-protocol-conformance/57040).
///
/// While removing continuation, the continuation should be cancelled.
@rethrows
internal protocol ContinuableCollectionActor: Actor {
/// The continuation item type in collection.
associatedtype Continuation: Continuable
/// The key type that is associated with each continuation item.
associatedtype Key: Hashable

/// Add continuation with the provided key to collection for tracking.
///
/// - Parameters:
/// - continuation: The continuation value to add.
/// - key: The key to associate continuation with.
/// - file: The file add request originates from.
/// - function: The function add request originates from.
/// - line: The line add request originates from.
/// - preinit: The pre-initialization handler to run
/// in the beginning of this method.
///
/// - Important: The pre-initialization handler must run
/// before any logic in this method.
func addContinuation(
_ continuation: Continuation, withKey key: Key,
file: String, function: String, line: UInt,
preinit: @Sendable () -> Void
)
/// Remove continuation with the associated key from collection out of tracking.
///
/// - Parameters:
/// - continuation: The continuation value to remove and cancel.
/// - key: The key for continuation to remove.
/// - file: The file remove request originates from.
/// - function: The function remove request originates from.
/// - line: The line remove request originates from.
func removeContinuation(
_ continuation: Continuation, withKey key: Key,
file: String, function: String, line: UInt
)
/// Suspends the current task, then calls the given closure with a continuation for the current task.
///
/// - Parameters:
/// - key: The key associated to task, that requested suspension.
/// - file: The file wait request originates from.
/// - function: The function wait request originates from.
/// - line: The line wait request originates from.
///
/// - Returns: The value continuation is resumed with.
/// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error.
func withPromisedContinuation(
withKey key: Key,
file: String, function: String, line: UInt
) async rethrows -> Continuation.Success
}

extension ContinuableCollectionActor
where
Self: AnyObject & Sendable, Continuation: TrackableContinuable & Sendable,
Continuation.Value: Sendable & ThrowingContinuable, Key: Sendable,
Key == Continuation.ID
{
/// 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.
///
/// - Parameters:
/// - key: The key associated to task, that requested suspension.
/// - 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`).
///
/// - Returns: The value continuation is resumed with.
/// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error.
@inlinable
nonisolated func withPromisedContinuation(
withKey key: Key,
file: String, function: String, line: UInt
) async rethrows -> Continuation.Success {
return try await Continuation.withCancellation(id: key) {
continuation in
Task { [weak self] in
await self?.removeContinuation(
continuation, withKey: key,
file: file, function: function, line: line
)
}
} operation: { continuation, preinit in
Task { [weak self] in
await self?.addContinuation(
continuation, withKey: key,
file: file, function: function, line: line,
preinit: preinit
)
}
}
}
}
#endif