Skip to content

Commit c66bb81

Browse files
committed
build: resolve concurrency check warnings
1 parent dd1b776 commit c66bb81

20 files changed

Lines changed: 494 additions & 271 deletions

Package.swift

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// swift-tools-version: 5.6
22

33
import PackageDescription
4+
import class Foundation.ProcessInfo
45

6+
let appleGitHub = "https://github.com/apple"
57
let package = Package(
68
name: "AsyncObjects",
79
platforms: [
@@ -17,20 +19,61 @@ let package = Package(
1719
),
1820
],
1921
dependencies: [
20-
.package(url: "https://github.com/apple/swift-collections.git", from: "1.0.0"),
21-
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
22-
.package(url: "https://github.com/apple/swift-format", from: "0.50600.1"),
22+
.package(url: "\(appleGitHub)/swift-collections.git", from: "1.0.0"),
23+
.package(url: "\(appleGitHub)/swift-docc-plugin", from: "1.0.0"),
24+
.package(url: "\(appleGitHub)/swift-format", from: "0.50600.1"),
2325
],
2426
targets: [
2527
.target(
2628
name: "AsyncObjects",
2729
dependencies: [
28-
.product(name: "OrderedCollections", package: "swift-collections"),
29-
]
30+
.product(
31+
name: "OrderedCollections",
32+
package: "swift-collections"
33+
),
34+
],
35+
swiftSettings: swiftSettings
3036
),
3137
.testTarget(
3238
name: "AsyncObjectsTests",
3339
dependencies: ["AsyncObjects"]
3440
),
3541
]
3642
)
43+
44+
var swiftSettings: [SwiftSetting] {
45+
var swiftSettings: [SwiftSetting] = []
46+
47+
if ProcessInfo.processInfo.environment[
48+
"SWIFTCI_CONCURRENCY_CHECKS"
49+
] != nil {
50+
swiftSettings.append(
51+
.unsafeFlags([
52+
"-Xfrontend",
53+
"-warn-concurrency",
54+
"-enable-actor-data-race-checks",
55+
"-require-explicit-sendable",
56+
])
57+
)
58+
}
59+
60+
if ProcessInfo.processInfo.environment[
61+
"SWIFTCI_WARNINGS_AS_ERRORS"
62+
] != nil {
63+
swiftSettings.append(
64+
.unsafeFlags([
65+
"-warnings-as-errors"
66+
])
67+
)
68+
}
69+
70+
if ProcessInfo.processInfo.environment[
71+
"ASYNCOBJECTS_USE_CHECKEDCONTINUATION"
72+
] != nil {
73+
swiftSettings.append(
74+
.define("ASYNCOBJECTS_USE_CHECKEDCONTINUATION")
75+
)
76+
}
77+
78+
return swiftSettings
79+
}

Sources/AsyncObjects/AsyncCountdownEvent.swift

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Foundation
1+
@preconcurrency import Foundation
22
import OrderedCollections
33

44
/// An event object that controls access to a resource between high and low priority tasks
@@ -41,13 +41,15 @@ public actor AsyncCountdownEvent: AsyncObject {
4141
/// Queued tasks are resumed from suspension when event is set and until current count exceeds limit.
4242
public var isSet: Bool { currentCount >= 0 && currentCount <= limit }
4343

44+
// MARK: Internal
45+
4446
/// Add continuation with the provided key in `continuations` map.
4547
///
4648
/// - Parameters:
4749
/// - continuation: The `continuation` to add.
4850
/// - key: The key in the map.
4951
@inlinable
50-
func addContinuation(
52+
func _addContinuation(
5153
_ continuation: Continuation,
5254
withKey key: UUID
5355
) {
@@ -59,7 +61,7 @@ public actor AsyncCountdownEvent: AsyncObject {
5961
///
6062
/// - Parameter key: The key in the map.
6163
@inlinable
62-
func removeContinuation(withKey key: UUID) {
64+
func _removeContinuation(withKey key: UUID) {
6365
let continuation = continuations.removeValue(forKey: key)
6466
continuation?.cancel()
6567
}
@@ -68,14 +70,14 @@ public actor AsyncCountdownEvent: AsyncObject {
6870
///
6971
/// - Parameter number: The number to decrement count by.
7072
@inlinable
71-
func decrementCount(by number: UInt = 1) {
73+
func _decrementCount(by number: UInt = 1) {
7274
guard currentCount > 0 else { return }
7375
currentCount -= number
7476
}
7577

7678
/// Resume previously waiting continuations for countdown event.
7779
@inlinable
78-
func resumeContinuations() {
80+
func _resumeContinuations() {
7981
while !continuations.isEmpty && isSet {
8082
let (_, continuation) = continuations.removeFirst()
8183
continuation.resume()
@@ -84,27 +86,29 @@ public actor AsyncCountdownEvent: AsyncObject {
8486
}
8587

8688
/// Suspends the current task, then calls the given closure with a throwing continuation for the current task.
87-
/// Continuation can be cancelled with error if current task is cancelled, by invoking `removeContinuation`.
89+
/// Continuation can be cancelled with error if current task is cancelled, by invoking `_removeContinuation`.
8890
///
89-
/// Spins up a new continuation and requests to track it with key by invoking `addContinuation`.
90-
/// This operation cooperatively checks for cancellation and reacting to it by invoking `removeContinuation`.
91+
/// Spins up a new continuation and requests to track it with key by invoking `_addContinuation`.
92+
/// This operation cooperatively checks for cancellation and reacting to it by invoking `_removeContinuation`.
9193
/// Continuation can be resumed with error and some cleanup code can be run here.
9294
///
9395
/// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error.
9496
@inlinable
95-
func withPromisedContinuation() async throws {
97+
func _withPromisedContinuation() async throws {
9698
let key = UUID()
9799
try await withTaskCancellationHandler { [weak self] in
98100
Task { [weak self] in
99-
await self?.removeContinuation(withKey: key)
101+
await self?._removeContinuation(withKey: key)
100102
}
101103
} operation: { () -> Continuation.Success in
102104
try await Continuation.with { continuation in
103-
self.addContinuation(continuation, withKey: key)
105+
self._addContinuation(continuation, withKey: key)
104106
}
105107
}
106108
}
107109

110+
// MARK: Public
111+
108112
/// Creates new countdown event with the limit count down up to and an initial count.
109113
/// By default, both limit and initial count are zero.
110114
///
@@ -141,7 +145,7 @@ public actor AsyncCountdownEvent: AsyncObject {
141145
/// are resumed from suspension until current count exceeds limit.
142146
public func reset() {
143147
self.currentCount = initialCount
144-
resumeContinuations()
148+
_resumeContinuations()
145149
}
146150

147151
/// Resets initial count and current count to specified value.
@@ -153,7 +157,7 @@ public actor AsyncCountdownEvent: AsyncObject {
153157
public func reset(to count: UInt) {
154158
initialCount = count
155159
self.currentCount = count
156-
resumeContinuations()
160+
_resumeContinuations()
157161
}
158162

159163
/// Registers a signal (decrements) with the countdown event.
@@ -171,8 +175,8 @@ public actor AsyncCountdownEvent: AsyncObject {
171175
///
172176
/// - Parameter count: The number of signals to register.
173177
public func signal(repeat count: UInt) {
174-
decrementCount(by: count)
175-
resumeContinuations()
178+
_decrementCount(by: count)
179+
_resumeContinuations()
176180
}
177181

178182
/// Waits for, or increments, a countdown event.
@@ -184,6 +188,6 @@ public actor AsyncCountdownEvent: AsyncObject {
184188
@Sendable
185189
public func wait() async {
186190
if isSet { currentCount += 1; return }
187-
try? await withPromisedContinuation()
191+
try? await _withPromisedContinuation()
188192
}
189193
}

Sources/AsyncObjects/AsyncEvent.swift

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Foundation
1+
@preconcurrency import Foundation
22

33
/// An object that controls execution of tasks depending on the signal state.
44
///
@@ -16,13 +16,15 @@ public actor AsyncEvent: AsyncObject {
1616
/// Indicates whether current state of event is signalled.
1717
private var signalled: Bool
1818

19+
// MARK: Internal
20+
1921
/// Add continuation with the provided key in `continuations` map.
2022
///
2123
/// - Parameters:
2224
/// - continuation: The `continuation` to add.
2325
/// - key: The key in the map.
2426
@inlinable
25-
func addContinuation(
27+
func _addContinuation(
2628
_ continuation: Continuation,
2729
withKey key: UUID
2830
) {
@@ -34,33 +36,35 @@ public actor AsyncEvent: AsyncObject {
3436
///
3537
/// - Parameter key: The key in the map.
3638
@inlinable
37-
func removeContinuation(withKey key: UUID) {
39+
func _removeContinuation(withKey key: UUID) {
3840
let continuation = continuations.removeValue(forKey: key)
3941
continuation?.cancel()
4042
}
4143

4244
/// Suspends the current task, then calls the given closure with a throwing continuation for the current task.
43-
/// Continuation can be cancelled with error if current task is cancelled, by invoking `removeContinuation`.
45+
/// Continuation can be cancelled with error if current task is cancelled, by invoking `_removeContinuation`.
4446
///
45-
/// Spins up a new continuation and requests to track it with key by invoking `addContinuation`.
46-
/// This operation cooperatively checks for cancellation and reacting to it by invoking `removeContinuation`.
47+
/// Spins up a new continuation and requests to track it with key by invoking `_addContinuation`.
48+
/// This operation cooperatively checks for cancellation and reacting to it by invoking `_removeContinuation`.
4749
/// Continuation can be resumed with error and some cleanup code can be run here.
4850
///
4951
/// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error.
5052
@inlinable
51-
func withPromisedContinuation() async throws {
53+
func _withPromisedContinuation() async throws {
5254
let key = UUID()
5355
try await withTaskCancellationHandler { [weak self] in
5456
Task { [weak self] in
55-
await self?.removeContinuation(withKey: key)
57+
await self?._removeContinuation(withKey: key)
5658
}
5759
} operation: { () -> Continuation.Success in
5860
try await Continuation.with { continuation in
59-
self.addContinuation(continuation, withKey: key)
61+
self._addContinuation(continuation, withKey: key)
6062
}
6163
}
6264
}
6365

66+
// MARK: Public
67+
6468
/// Creates a new event with signal state provided.
6569
/// By default, event is initially in signalled state.
6670
///
@@ -96,6 +100,6 @@ public actor AsyncEvent: AsyncObject {
96100
@Sendable
97101
public func wait() async {
98102
guard !signalled else { return }
99-
try? await withPromisedContinuation()
103+
try? await _withPromisedContinuation()
100104
}
101105
}

Sources/AsyncObjects/AsyncObject.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Foundation
22

33
/// A result value indicating whether a task finished before a specified time.
44
@frozen
5-
public enum TaskTimeoutResult: Hashable {
5+
public enum TaskTimeoutResult: Hashable, Sendable {
66
/// Indicates that a task successfully finished
77
/// before the specified time elapsed.
88
case success
@@ -240,7 +240,8 @@ public func waitForTaskCompletion(
240240
}
241241
}
242242
group.addTask {
243-
(try? await Task.sleep(nanoseconds: timeout + 1_000)) == nil
243+
await Task.yield()
244+
return (try? await Task.sleep(nanoseconds: timeout + 1_000)) == nil
244245
}
245246
if let result = await group.next() {
246247
timedOut = !result

Sources/AsyncObjects/AsyncSemaphore.swift

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Foundation
1+
@preconcurrency import Foundation
22
import OrderedCollections
33

44
/// An object that controls access to a resource across multiple task contexts through use of a traditional counting semaphore.
@@ -25,13 +25,15 @@ public actor AsyncSemaphore: AsyncObject {
2525
@usableFromInline
2626
private(set) var count: Int
2727

28+
// MARK: Internal
29+
2830
/// Add continuation with the provided key in `continuations` map.
2931
///
3032
/// - Parameters:
3133
/// - continuation: The `continuation` to add.
3234
/// - key: The key in the map.
3335
@inlinable
34-
func addContinuation(
36+
func _addContinuation(
3537
_ continuation: Continuation,
3638
withKey key: UUID
3739
) {
@@ -43,41 +45,43 @@ public actor AsyncSemaphore: AsyncObject {
4345
///
4446
/// - Parameter key: The key in the map.
4547
@inlinable
46-
func removeContinuation(withKey key: UUID) {
48+
func _removeContinuation(withKey key: UUID) {
4749
let continuation = continuations.removeValue(forKey: key)
4850
continuation?.cancel()
49-
incrementCount()
51+
_incrementCount()
5052
}
5153

5254
/// Increments semaphore count within limit provided.
5355
@inlinable
54-
func incrementCount() {
56+
func _incrementCount() {
5557
guard count < limit else { return }
5658
count += 1
5759
}
5860

5961
/// Suspends the current task, then calls the given closure with a throwing continuation for the current task.
60-
/// Continuation can be cancelled with error if current task is cancelled, by invoking `removeContinuation`.
62+
/// Continuation can be cancelled with error if current task is cancelled, by invoking `_removeContinuation`.
6163
///
62-
/// Spins up a new continuation and requests to track it with key by invoking `addContinuation`.
63-
/// This operation cooperatively checks for cancellation and reacting to it by invoking `removeContinuation`.
64+
/// Spins up a new continuation and requests to track it with key by invoking `_addContinuation`.
65+
/// This operation cooperatively checks for cancellation and reacting to it by invoking `_removeContinuation`.
6466
/// Continuation can be resumed with error and some cleanup code can be run here.
6567
///
6668
/// - Throws: If `resume(throwing:)` is called on the continuation, this function throws that error.
6769
@inlinable
68-
func withPromisedContinuation() async throws {
70+
func _withPromisedContinuation() async throws {
6971
let key = UUID()
7072
try await withTaskCancellationHandler { [weak self] in
7173
Task { [weak self] in
72-
await self?.removeContinuation(withKey: key)
74+
await self?._removeContinuation(withKey: key)
7375
}
7476
} operation: { () -> Continuation.Success in
7577
try await Continuation.with { continuation in
76-
self.addContinuation(continuation, withKey: key)
78+
self._addContinuation(continuation, withKey: key)
7779
}
7880
}
7981
}
8082

83+
// MARK: Public
84+
8185
/// Creates new counting semaphore with an initial value.
8286
/// By default, initial value is zero.
8387
///
@@ -100,7 +104,7 @@ public actor AsyncSemaphore: AsyncObject {
100104
/// If the previous value was less than zero,
101105
/// current task is resumed from suspension.
102106
public func signal() {
103-
incrementCount()
107+
_incrementCount()
104108
guard !continuations.isEmpty else { return }
105109
let (_, continuation) = continuations.removeFirst()
106110
continuation.resume()
@@ -114,6 +118,6 @@ public actor AsyncSemaphore: AsyncObject {
114118
public func wait() async {
115119
count -= 1
116120
if count > 0 { return }
117-
try? await withPromisedContinuation()
121+
try? await _withPromisedContinuation()
118122
}
119123
}

0 commit comments

Comments
 (0)