Skip to content

Commit

Permalink
chore: only use swift 6
Browse files Browse the repository at this point in the history
  • Loading branch information
ph1ps committed Nov 11, 2024
1 parent cde9510 commit 435251f
Show file tree
Hide file tree
Showing 2 changed files with 5 additions and 200 deletions.
11 changes: 5 additions & 6 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version: 5.9
// swift-tools-version: 6.0

import PackageDescription

Expand All @@ -13,16 +13,15 @@ let package = Package(
],
targets: [
.target(
name: "Deadline",
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
name: "Deadline"
),
.testTarget(
name: "DeadlineTests",
dependencies: [
"Deadline",
.product(name: "Clocks", package: "swift-clocks")
],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
]
)
]
],
swiftLanguageModes: [.v6]
)
194 changes: 0 additions & 194 deletions Sources/Deadline/Deadline.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ enum DeadlineState<T>: Sendable where T: Sendable {
/// An error indicating that the deadline has passed and the operation did not complete.
public struct DeadlineExceededError: Error { }

#if swift(>=6.0)
/// Race the given operation against a deadline.
///
/// This function provides a mechanism for enforcing timeouts on asynchronous operations that lack native deadline support. It creates a `TaskGroup` with two concurrent tasks: the provided operation and a sleep task.
Expand Down Expand Up @@ -203,196 +202,3 @@ public func deadline<R>(
) async throws -> R where R: Sendable {
try await deadline(until: instant, tolerance: tolerance, clock: ContinuousClock(), isolation: isolation, operation: operation)
}
#else
/// Race the given operation against a deadline.
///
/// This function provides a mechanism for enforcing timeouts on asynchronous operations that lack native deadline support. It creates a `TaskGroup` with two concurrent tasks: the provided operation and a sleep task.
///
/// - Parameters:
/// - instant: The absolute deadline for the operation to complete.
/// - tolerance: The allowed tolerance for the deadline.
/// - clock: The clock used for timing the operation.
/// - operation: The asynchronous operation to be executed.
///
/// - Returns: The result of the operation if it completes before the deadline.
/// - Throws: `DeadlineExceededError`, if the operation fails to complete before the deadline and errors thrown by the operation or clock.
///
/// ## Examples
/// To fully understand this, let's illustrate the 3 outcomes of this function:
///
/// ### Outcome 1
/// The operation finishes in time:
/// ```swift
/// let result = try await deadline(until: .now + .seconds(5)) {
/// // Simulate long running task
/// try await Task.sleep(for: .seconds(1))
/// return "success"
/// }
/// ```
/// As you'd expect, result will be "success". The same applies when your operation fails in time:
/// ```swift
/// let result = try await deadline(until: .now + .seconds(5)) {
/// // Simulate long running task
/// try await Task.sleep(for: .seconds(1))
/// throw CustomError()
/// }
/// ```
/// This will throw `CustomError`.
///
/// ## Outcome 2
/// The operation does not finish in time:
/// ```swift
/// let result = try await deadline(until: .now + .seconds(1)) {
/// // Simulate even longer running task
/// try await Task.sleep(for: .seconds(5))
/// return "success"
/// }
/// ```
/// This will throw `DeadlineExceededError` because the operation will not finish in time.
///
/// ## Outcome 3
/// The parent task was cancelled:
/// ```swift
/// let task = Task {
/// do {
/// try await deadline(until: .now + .seconds(5)) {
/// try await URLSession.shared.data(from: url)
/// }
/// } catch {
/// print(error)
/// }
/// }
///
/// task.cancel()
/// ```
/// The print is guaranteed to print `URLError(.cancelled)`.
/// - Important: The operation closure must support cooperative cancellation. Otherwise, the deadline will not be respected.
public func deadline<C, R>(
until instant: C.Instant,
tolerance: C.Instant.Duration? = nil,
clock: C,
operation: @Sendable () async throws -> R
) async throws -> R where C: Clock, R: Sendable {

// NB: This is safe to use, because the closure will not escape the context of this function.
let result = await withoutActuallyEscaping(operation) { operation in
await withTaskGroup(
of: DeadlineState<R>.self,
returning: Result<R, any Error>.self
) { taskGroup in

taskGroup.addTask {
do {
let result = try await operation()
return .operationResult(.success(result))
} catch {
return .operationResult(.failure(error))
}
}

taskGroup.addTask {
do {
try await Task.sleep(until: instant, tolerance: tolerance, clock: clock)
return .sleepResult(.success(false))
} catch where Task.isCancelled {
return .sleepResult(.success(true))
} catch {
return .sleepResult(.failure(error))
}
}

defer {
taskGroup.cancelAll()
}

for await next in taskGroup {
switch next {
case .operationResult(let result):
return result
case .sleepResult(.success(false)):
return .failure(DeadlineExceededError())
case .sleepResult(.success(true)):
continue
case .sleepResult(.failure(let error)):
return .failure(error)
}
}

preconditionFailure("Invalid state")
}
}

return try result.get()
}

/// Race the given operation against a deadline.
///
/// This function provides a mechanism for enforcing timeouts on asynchronous operations that lack native deadline support. It creates a `TaskGroup` with two concurrent tasks: the provided operation and a sleep task.
/// `ContinuousClock` will be used as the default clock.
///
/// - Parameters:
/// - instant: The absolute deadline for the operation to complete.
/// - tolerance: The allowed tolerance for the deadline.
/// - operation: The asynchronous operation to be executed.
///
/// - Returns: The result of the operation if it completes before the deadline.
/// - Throws: `DeadlineExceededError`, if the operation fails to complete before the deadline and errors thrown by the operation or clock.
///
/// ## Examples
/// To fully understand this, let's illustrate the 3 outcomes of this function:
///
/// ### Outcome 1
/// The operation finishes in time:
/// ```swift
/// let result = try await deadline(until: .now + .seconds(5)) {
/// // Simulate long running task
/// try await Task.sleep(for: .seconds(1))
/// return "success"
/// }
/// ```
/// As you'd expect, result will be "success". The same applies when your operation fails in time:
/// ```swift
/// let result = try await deadline(until: .now + .seconds(5)) {
/// // Simulate long running task
/// try await Task.sleep(for: .seconds(1))
/// throw CustomError()
/// }
/// ```
/// This will throw `CustomError`.
///
/// ## Outcome 2
/// The operation does not finish in time:
/// ```swift
/// let result = try await deadline(until: .now + .seconds(1)) {
/// // Simulate even longer running task
/// try await Task.sleep(for: .seconds(5))
/// return "success"
/// }
/// ```
/// This will throw `DeadlineExceededError` because the operation will not finish in time.
///
/// ## Outcome 3
/// The parent task was cancelled:
/// ```swift
/// let task = Task {
/// do {
/// try await deadline(until: .now + .seconds(5)) {
/// try await URLSession.shared.data(from: url)
/// }
/// } catch {
/// print(error)
/// }
/// }
///
/// task.cancel()
/// ```
/// The print is guaranteed to print `URLError(.cancelled)`.
/// - Important: The operation closure must support cooperative cancellation. Otherwise, the deadline will not be respected.
public func deadline<R>(
until instant: ContinuousClock.Instant,
tolerance: ContinuousClock.Instant.Duration? = nil,
operation: @Sendable () async throws -> R
) async throws -> R where R: Sendable {
try await deadline(until: instant, tolerance: tolerance, clock: ContinuousClock(), operation: operation)
}
#endif

0 comments on commit 435251f

Please sign in to comment.