Skip to content

Commit eddf9b8

Browse files
committed
Make FIFOQueue, ActorQueue, and tests pass strict concurrency checking
1 parent 5e26f27 commit eddf9b8

File tree

6 files changed

+16
-11
lines changed

6 files changed

+16
-11
lines changed

AsyncQueue.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'AsyncQueue'
3-
s.version = '0.2.0'
3+
s.version = '0.3.0'
44
s.license = 'MIT'
55
s.summary = 'A queue that enables ordered sending of events from synchronous to asynchronous code.'
66
s.homepage = 'https://github.com/dfed/swift-async-queue'

Package.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ let package = Package(
2222
dependencies: []),
2323
.testTarget(
2424
name: "AsyncQueueTests",
25-
dependencies: ["AsyncQueue"]),
25+
dependencies: ["AsyncQueue"],
26+
swiftSettings: [
27+
// TODO: Adopt `enableUpcomingFeature` once available.
28+
// https://github.com/apple/swift-evolution/blob/main/proposals/0362-piecemeal-future-features.md
29+
.unsafeFlags(["-Xfrontend", "-strict-concurrency=complete"])
30+
]),
2631
]
2732
)

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ FIFO execution has a key downside: the queue must wait for all previously enqueu
9090
Use an `ActorQueue` to send ordered asynchronous tasks to an `actor`’s isolated context from nonisolated or synchronous contexts. Tasks sent to an actor queue are guaranteed to begin executing in the order in which they are enqueued. However, unlike a `FIFOQueue`, execution order is guaranteed only until the first [suspension point](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html#ID639) within the enqueued task. An `ActorQueue` executes tasks within the its adopted actor’s isolated context, resulting in `ActorQueue` task execution having the same properties as `actor` code execution: code between suspension points is executed atomically, and tasks sent to a single `ActorQueue` can await results from the queue without deadlocking.
9191

9292
An instance of an `ActorQueue` is designed to be utilized by a single `actor` instance: tasks sent to an `ActorQueue` utilize the isolated context of the queue‘s adopted `actor` to serialize tasks. As such, there are a few requirements that must be met when dealing with an `ActorQueue`:
93-
1. The lifecycle of any `ActorQueue` should not exceed the lifecycle of its `actor`. It is strongly recommended that an `ActorQueue` be a `let` constant on the adopted `actor`. Enqueuing a task to an `ActorQueue` isntance after its adopted `actor` has been deallocated will result in a crash.
93+
1. The lifecycle of any `ActorQueue` should not exceed the lifecycle of its `actor`. It is strongly recommended that an `ActorQueue` be a `private let` constant on the adopted `actor`. Enqueuing a task to an `ActorQueue` isntance after its adopted `actor` has been deallocated will result in a crash.
9494
2. An `actor` utilizing an `ActorQueue` should set the adopted execution context of the queue to `self` within the `actor`’s `init`. Failing to set an adopted execution context prior to enqueuing work on an `ActorQueue` will result in a crash.
9595

9696
An `ActorQueue` can easily enqueue tasks that execute on an actor’s isolated context from a nonisolated context in order:

Sources/AsyncQueue/ActorQueue.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@
5050
/// }
5151
/// ```
5252
///
53+
/// - Warning: The `ActorQueue`'s conformance to `@unchecked Sendable` is safe if and only if `adoptExecutionContext(of:)` is called only from the adopted actor's `init` method.
5354
/// - Precondition: The lifecycle of an `ActorQueue` must not exceed that of the adopted actor.
54-
public final class ActorQueue<ActorType: Actor> {
55+
public final class ActorQueue<ActorType: Actor>: @unchecked Sendable {
5556

5657
// MARK: Initialization
5758

@@ -150,7 +151,6 @@ public final class ActorQueue<ActorType: Actor> {
150151
let executionContext: ActorType
151152
let task: @Sendable (isolated ActorType) async -> Void
152153
}
153-
154154
}
155155

156156
extension Actor {

Sources/AsyncQueue/FIFOQueue.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public final class FIFOQueue: Sendable {
8585
/// - isolatedActor: The actor within which the task is isolated.
8686
/// - task: The task to enqueue.
8787
/// - Returns: The value returned from the enqueued task.
88-
public func enqueueAndWait<ActorType: Actor, T>(on isolatedActor: isolated ActorType, _ task: @escaping @Sendable (isolated ActorType) async -> T) async -> T {
88+
public func enqueueAndWait<ActorType: Actor, T: Sendable>(on isolatedActor: isolated ActorType, _ task: @escaping @Sendable (isolated ActorType) async -> T) async -> T {
8989
await withUnsafeContinuation { continuation in
9090
taskStreamContinuation.yield {
9191
continuation.resume(returning: await task(isolatedActor))
@@ -115,7 +115,7 @@ public final class FIFOQueue: Sendable {
115115
/// - isolatedActor: The actor within which the task is isolated.
116116
/// - task: The task to enqueue.
117117
/// - Returns: The value returned from the enqueued task.
118-
public func enqueueAndWait<ActorType: Actor, T>(on isolatedActor: isolated ActorType, _ task: @escaping @Sendable (isolated ActorType) async throws -> T) async throws -> T {
118+
public func enqueueAndWait<ActorType: Actor, T: Sendable>(on isolatedActor: isolated ActorType, _ task: @escaping @Sendable (isolated ActorType) async throws -> T) async throws -> T {
119119
try await withUnsafeThrowingContinuation { continuation in
120120
taskStreamContinuation.yield {
121121
do {

Tests/AsyncQueueTests/ActorQueueTests.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,17 @@ final class ActorQueueTests: XCTestCase {
6565

6666
func test_enqueue_taskParameterIsAdoptedActor() async {
6767
let semaphore = Semaphore()
68-
systemUnderTest.enqueue { counter in
69-
XCTAssertTrue(counter === self.counter)
68+
systemUnderTest.enqueue { [storedCounter = counter] counter in
69+
XCTAssertTrue(counter === storedCounter)
7070
await semaphore.signal()
7171
}
7272

7373
await semaphore.wait()
7474
}
7575

7676
func test_enqueueAndWait_taskParameterIsAdoptedActor() async {
77-
await systemUnderTest.enqueueAndWait { counter in
78-
XCTAssertTrue(counter === self.counter)
77+
await systemUnderTest.enqueueAndWait { [storedCounter = counter] counter in
78+
XCTAssertTrue(counter === storedCounter)
7979
}
8080
}
8181

0 commit comments

Comments
 (0)