Skip to content

Commit

Permalink
Add tests and small bugfix
Browse files Browse the repository at this point in the history
  • Loading branch information
philippzagar committed Feb 29, 2024
1 parent 913d5c0 commit 2d659cd
Show file tree
Hide file tree
Showing 2 changed files with 425 additions and 13 deletions.
54 changes: 41 additions & 13 deletions Sources/SpeziFoundation/Semaphore/AsyncSemaphore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import Foundation
/// ```
///
/// - Warning: `cancelAll` will trigger a runtime error if it attempts to cancel tasks that are not cancellable.
final public class AsyncSemaphore {
final public class AsyncSemaphore: @unchecked Sendable {
private enum Suspension {
case cancelable(UnsafeContinuation<Void, Error>)
case regular(UnsafeContinuation<Void, Never>)
Expand All @@ -65,10 +65,15 @@ final public class AsyncSemaphore {
}
}
}

private struct SuspendedTask: Identifiable {
let id: UUID
let suspension: Suspension
}


private var value: Int
private var suspendedTasks: [Suspension] = []
private var suspendedTasks: [SuspendedTask] = []
private let nsLock = NSLock()


Expand All @@ -94,7 +99,7 @@ final public class AsyncSemaphore {
}

await withUnsafeContinuation { continuation in
suspendedTasks.append(.regular(continuation))
suspendedTasks.append(SuspendedTask(id: UUID(), suspension: .regular(continuation)))
unlock()
}
}
Expand Down Expand Up @@ -123,16 +128,39 @@ final public class AsyncSemaphore {
return
}

let id = UUID()

try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation<Void, Error>) in
if Task.isCancelled {
value += 1 // restore the value
unlock()
try await withTaskCancellationHandler {
try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation<Void, Error>) in
if Task.isCancelled {
value += 1 // restore the value
unlock()

continuation.resume(throwing: CancellationError())

Check warning on line 139 in Sources/SpeziFoundation/Semaphore/AsyncSemaphore.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziFoundation/Semaphore/AsyncSemaphore.swift#L136-L139

Added lines #L136 - L139 were not covered by tests
} else {
suspendedTasks.append(SuspendedTask(id: id, suspension: .cancelable(continuation)))
unlock()
}
}
} onCancel: {
self.lock()

value += 1

guard let index = suspendedTasks.firstIndex(where: { $0.id == id }) else {
preconditionFailure("Inconsistent internal state reached")

Check warning on line 151 in Sources/SpeziFoundation/Semaphore/AsyncSemaphore.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziFoundation/Semaphore/AsyncSemaphore.swift#L151

Added line #L151 was not covered by tests
}

let task = suspendedTasks[index]
suspendedTasks.remove(at: index)

unlock()

switch task.suspension {
case .regular:
preconditionFailure("Tried to cancel a task that was not cancellable!")

Check warning on line 161 in Sources/SpeziFoundation/Semaphore/AsyncSemaphore.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziFoundation/Semaphore/AsyncSemaphore.swift#L161

Added line #L161 was not covered by tests
case let .cancelable(continuation):
continuation.resume(throwing: CancellationError())
} else {
suspendedTasks.append(.cancelable(continuation))
unlock()
}
}
}
Expand All @@ -157,7 +185,7 @@ final public class AsyncSemaphore {
suspendedTasks.removeFirst()
unlock()

first.resume()
first.suspension.resume()
return true
}

Expand All @@ -175,7 +203,7 @@ final public class AsyncSemaphore {
unlock()

for task in tasks {
task.resume()
task.suspension.resume()
}
}

Check warning on line 208 in Sources/SpeziFoundation/Semaphore/AsyncSemaphore.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziFoundation/Semaphore/AsyncSemaphore.swift#L195-L208

Added lines #L195 - L208 were not covered by tests

Expand All @@ -195,7 +223,7 @@ final public class AsyncSemaphore {
unlock()

for task in tasks {
switch task {
switch task.suspension {
case .regular:
preconditionFailure("Tried to cancel a task that was not cancellable!")
case let .cancelable(continuation):
Expand Down
Loading

0 comments on commit 2d659cd

Please sign in to comment.