Skip to content

Instantly share code, notes, and snippets.

@rjchatfield
Created March 6, 2022 21:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rjchatfield/3d4ffecc4263adc91c0dfa2d1eeeef85 to your computer and use it in GitHub Desktop.
Save rjchatfield/3d4ffecc4263adc91c0dfa2d1eeeef85 to your computer and use it in GitHub Desktop.
/*
Inspired by https://forums.swift.org/t/running-an-async-task-with-a-timeout/49733
*/
/// - Throws: Throws `TimedOutError` if the timeout expires before `work` completes.
/// If `work` throws an error before the timeout expires, that error is propagated to the caller.
func withTimeout<R>(
_ maxDuration: TimeInterval,
returning returnType: R.Type = R.self,
work: @Sendable @escaping () async throws -> R
) async throws -> R {
try await race(
returning: R.self,
task1: work,
priority2: .high,
task2: {
try await Task.sleep(nanoseconds: UInt64(maxDuration * 1_000_000_000))
// We’ve reached the timeout.
// Would have thrown CancellationError() if it were cancelled during sleep.
throw TimedOutError()
}
)
}
/// Returns either the `Success` or `Failure` of the first task to finish. The remaining task will be cancelled and ignored.
func race<RaceResult>(
returning returnType: RaceResult.Type = RaceResult.self,
priority1: TaskPriority? = nil,
task1: @Sendable @escaping () async throws -> RaceResult,
priority2: TaskPriority? = nil,
task2: @Sendable @escaping () async throws -> RaceResult
) async throws -> RaceResult {
try await withThrowingTaskGroup(of: RaceResult.self, returning: RaceResult.self) { group in
// Start actual work.
let added1 = group.addTaskUnlessCancelled(priority: priority1, operation: task1)
let added2 = group.addTaskUnlessCancelled(priority: priority2, operation: task2)
// First finished child task wins, cancel the other task.
guard added1 || added2,
let result = try await group.next(),
!Task.isCancelled
else {
// If no tasks were added because task was cancelled, then re-throw cancellation.
throw CancellationError()
}
group.cancelAll()
return result
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment