[Concurrency] Add clock traits.

Remove the hacky support for mapping clocks to a Dispatch clock ID,
in favour of clocks publishing traits and having the Dispatch
executor select the clock on the basis of those traits.
This commit is contained in:
Alastair Houghton
2025-03-10 12:13:16 +00:00
parent 55afa47bea
commit d89ea190bb
5 changed files with 65 additions and 27 deletions

View File

@@ -42,14 +42,9 @@ public protocol Clock<Duration>: Sendable {
func sleep(until deadline: Instant, tolerance: Instant.Duration?) async throws func sleep(until deadline: Instant, tolerance: Instant.Duration?) async throws
#endif #endif
#if !$Embedded /// The traits associated with this clock instance.
/// Choose which Dispatch clock to use with DispatchExecutor
///
/// This controls which Dispatch clock is used to enqueue delayed jobs
/// when using this Clock.
@available(SwiftStdlib 6.2, *) @available(SwiftStdlib 6.2, *)
var dispatchClockID: DispatchClockID { get } var traits: ClockTraits { get }
#endif
/// Convert a Clock-specific Duration to a Swift Duration /// Convert a Clock-specific Duration to a Swift Duration
/// ///
@@ -138,12 +133,6 @@ extension Clock {
@available(SwiftStdlib 6.2, *) @available(SwiftStdlib 6.2, *)
extension Clock { extension Clock {
#if !$Embedded
public var dispatchClockID: DispatchClockID {
return .suspending
}
#endif
// For compatibility, return `nil` if this is not implemented // For compatibility, return `nil` if this is not implemented
public func convert(from duration: Duration) -> Swift.Duration? { public func convert(from duration: Duration) -> Swift.Duration? {
return nil return nil
@@ -198,6 +187,43 @@ extension Clock {
} }
#endif #endif
/// Represents traits of a particular Clock implementation.
///
/// Clocks may be of a number of different varieties; executors will likely
/// have specific clocks that they can use to schedule jobs, and will
/// therefore need to be able to convert timestamps to an appropriate clock
/// when asked to enqueue a job with a delay or deadline.
///
/// Choosing a clock in general requires the ability to tell which of their
/// clocks best matches the clock that the user is trying to specify a
/// time or delay in. Executors are expected to do this on a best effort
/// basis.
@available(SwiftStdlib 6.2, *)
public struct ClockTraits: OptionSet {
public let rawValue: Int32
public init(rawValue: Int32) {
self.rawValue = rawValue
}
/// Clocks with this trait continue running while the machine is asleep.
public static let continuous = ClockTraits(rawValue: 1 << 0)
/// Indicates that a clock's time will only ever increase.
public static let monotonic = ClockTraits(rawValue: 1 << 1)
/// Clocks with this trait are tied to "wall time".
public static let wallTime = ClockTraits(rawValue: 1 << 2)
}
extension Clock {
/// The traits associated with this clock instance.
@available(SwiftStdlib 6.2, *)
var traits: ClockTraits {
return []
}
}
enum _ClockID: Int32 { enum _ClockID: Int32 {
case continuous = 1 case continuous = 1
case suspending = 2 case suspending = 2

View File

@@ -100,6 +100,12 @@ extension ContinuousClock: Clock {
) )
} }
/// The continuous clock is continuous and monotonic
@available(SwiftStdlib 6.2, *)
public var traits: ClockTraits {
return [.continuous, .monotonic]
}
#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY #if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
/// Suspend task execution until a given deadline within a tolerance. /// Suspend task execution until a given deadline within a tolerance.
/// If no tolerance is specified then the system may adjust the deadline /// If no tolerance is specified then the system may adjust the deadline

View File

@@ -178,8 +178,7 @@ protocol DispatchExecutor: Executor {
} }
/// An enumeration identifying one of the Dispatch-supported clocks /// An enumeration identifying one of the Dispatch-supported clocks
@available(SwiftStdlib 6.2, *) enum DispatchClockID: CInt {
public enum DispatchClockID: CInt {
case suspending = 1 case suspending = 1
case continuous = 2 case continuous = 2
} }
@@ -189,18 +188,7 @@ extension DispatchExecutor {
func timestamp<C: Clock>(for instant: C.Instant, clock: C) func timestamp<C: Clock>(for instant: C.Instant, clock: C)
-> (clockID: DispatchClockID, seconds: Int64, nanoseconds: Int64) { -> (clockID: DispatchClockID, seconds: Int64, nanoseconds: Int64) {
let clockID = clock.dispatchClockID if clock.traits.contains(.continuous) {
switch clockID {
case .suspending:
let dispatchClock: SuspendingClock = .suspending
let instant = dispatchClock.convert(instant: instant, from: clock)!
let (seconds, attoseconds) = instant._value.components
let nanoseconds = attoseconds / 1_000_000_000
return (clockID: .suspending,
seconds: Int64(seconds),
nanoseconds: Int64(nanoseconds))
case .continuous:
let dispatchClock: ContinuousClock = .continuous let dispatchClock: ContinuousClock = .continuous
let instant = dispatchClock.convert(instant: instant, from: clock)! let instant = dispatchClock.convert(instant: instant, from: clock)!
let (seconds, attoseconds) = instant._value.components let (seconds, attoseconds) = instant._value.components
@@ -208,6 +196,14 @@ extension DispatchExecutor {
return (clockID: .continuous, return (clockID: .continuous,
seconds: Int64(seconds), seconds: Int64(seconds),
nanoseconds: Int64(nanoseconds)) nanoseconds: Int64(nanoseconds))
} else {
let dispatchClock: SuspendingClock = .suspending
let instant = dispatchClock.convert(instant: instant, from: clock)!
let (seconds, attoseconds) = instant._value.components
let nanoseconds = attoseconds / 1_000_000_000
return (clockID: .suspending,
seconds: Int64(seconds),
nanoseconds: Int64(nanoseconds))
} }
} }

View File

@@ -87,6 +87,12 @@ extension SuspendingClock: Clock {
return Duration(_seconds: seconds, nanoseconds: nanoseconds) return Duration(_seconds: seconds, nanoseconds: nanoseconds)
} }
/// The suspending clock is monotonic
@available(SwiftStdlib 6.2, *)
public var traits: ClockTraits {
return [.monotonic]
}
#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY #if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
/// Suspend task execution until a given deadline within a tolerance. /// Suspend task execution until a given deadline within a tolerance.
/// If no tolerance is specified then the system may adjust the deadline /// If no tolerance is specified then the system may adjust the deadline

View File

@@ -11,6 +11,7 @@
import StdlibUnittest import StdlibUnittest
@available(SwiftStdlib 6.2, *)
struct TickingClock: Clock { struct TickingClock: Clock {
struct Duration: DurationProtocol { struct Duration: DurationProtocol {
var ticks: Int var ticks: Int
@@ -57,6 +58,7 @@ struct TickingClock: Clock {
private var _now: Instant private var _now: Instant
var now: Instant { return _now } var now: Instant { return _now }
var minimumResolution: Duration { return Duration(ticks: 1) } var minimumResolution: Duration { return Duration(ticks: 1) }
var traits: ClockTraits { [.monotonic] }
init() { init() {
_now = Instant(ticksFromStart: 0) _now = Instant(ticksFromStart: 0)
@@ -87,6 +89,7 @@ struct TickingClock: Clock {
} }
} }
@available(SwiftStdlib 6.2, *)
struct TockingClock: Clock { struct TockingClock: Clock {
struct Duration: DurationProtocol { struct Duration: DurationProtocol {
var tocks: Int var tocks: Int
@@ -133,6 +136,7 @@ struct TockingClock: Clock {
private var _now: Instant private var _now: Instant
var now: Instant { return _now } var now: Instant { return _now }
var minimumResolution: Duration { return Duration(tocks: 1) } var minimumResolution: Duration { return Duration(tocks: 1) }
var traits: ClockTraits { [.monotonic] }
init() { init() {
_now = Instant(tocksFromStart: 1000) _now = Instant(tocksFromStart: 1000)