blob: 91f2ac39ba048bec69678553f91bec2671f781c7 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
//
// Task+.swift
// PacketTunnelCore
//
// Created by pronebird on 11/09/2023.
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//
import Foundation
import MullvadTypes
private typealias TaskCancellationError = CancellationError
extension Task where Success == Never, Failure == Never {
/**
Suspends the current task for at least the given duration.
Negative durations are clamped to zero.
- Parameter duration: duration that determines how long the task should be suspended.
*/
@available(iOS, introduced: 15.0, obsoleted: 16.0, message: "Replace with Task.sleep(for:tolerance:clock:).")
static func sleep(duration: Duration) async throws {
let millis = UInt64(max(0, duration.milliseconds))
let nanos = millis.saturatingMultiplication(1_000_000)
try await Task.sleep(nanoseconds: nanos)
}
/**
Suspends the current task for the given duration.
Negative durations are clamped to zero.
- Parameter duration: duration that determines how long the task should be suspended.
*/
@available(iOS, introduced: 15.0, obsoleted: 16.0, message: "Replace with Task.sleep(for:tolerance:clock:).")
static func sleepUsingContinuousClock(for duration: Duration) async throws {
nonisolated(unsafe) let timer = DispatchSource.makeTimerSource()
try await withTaskCancellationHandler {
try await withCheckedThrowingContinuation { continuation in
// The `continuation` handler should never be `resume`'d more than once.
// Setting the eventHandler on the timer after it has been cancelled will be ignored.
// https://github.com/apple/swift-corelibs-libdispatch/blob/77766345740dfe3075f2f60bead854b29b0cfa24/src/source.c#L338
// Therefore, set a flag indicating `resume` has already been called to avoid `resume`ing more than once.
// Cancelling the timer does not cancel an event handler that is already running however,
// the cancel handler will be scheduled after the event handler has finished.
// https://developer.apple.com/documentation/dispatch/1385604-dispatch_source_cancel
// Therefore, there it is safe to do this here.
var hasResumed = false
timer.setEventHandler {
hasResumed = true
continuation.resume()
}
timer.setCancelHandler {
guard hasResumed == false else { return }
continuation.resume(throwing: TaskCancellationError())
}
timer.schedule(wallDeadline: .now() + DispatchTimeInterval.milliseconds(duration.milliseconds))
timer.activate()
}
} onCancel: {
timer.cancel()
}
}
}
|