Skip to content

Commit 6046286

Browse files
committed
[Concurrency] Updates after second SE pitch.
We no longer attempt to convert timestamps from the passed-in `Clock` in order to allow any clock to work with any executor. Instead, executors that do not recognise a clock should call the `enqueue` function on that `Clock`, which lets the `Clock` itself decide how to proceed. Additionally, rename `SchedulableExecutor` to `SchedulingExecutor`.
1 parent 3a8399d commit 6046286

File tree

17 files changed

+642
-795
lines changed

17 files changed

+642
-795
lines changed

stdlib/public/Concurrency/Clock.swift

Lines changed: 122 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -34,57 +34,154 @@ import Swift
3434
public protocol Clock<Duration>: Sendable {
3535
associatedtype Duration
3636
associatedtype Instant: InstantProtocol where Instant.Duration == Duration
37+
associatedtype CanonicalClock: Clock = Self
3738

3839
var now: Instant { get }
3940
var minimumResolution: Instant.Duration { get }
4041

4142
#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
4243
func sleep(until deadline: Instant, tolerance: Instant.Duration?) async throws
43-
#endif
4444

45-
/// The traits associated with this clock instance.
45+
/// Run the given job on an unspecified executor at some point
46+
/// after the given instant.
47+
///
48+
/// Parameters:
49+
///
50+
/// - job: The job we wish to run
51+
/// - at instant: The time at which we would like it to run.
52+
/// - tolerance: The ideal maximum delay we are willing to tolerate.
53+
///
4654
@available(StdlibDeploymentTarget 6.2, *)
47-
var traits: ClockTraits { get }
55+
func run(_ job: consuming ExecutorJob,
56+
at instant: Instant, tolerance: Duration?)
4857

49-
/// Convert a Clock-specific Duration to a Swift Duration
58+
/// Enqueue the given job on the specified executor at some point after the
59+
/// given instant.
5060
///
51-
/// Some clocks may define `C.Duration` to be something other than a
52-
/// `Swift.Duration`, but that makes it tricky to convert timestamps
53-
/// between clocks, which is something we want to be able to support.
54-
/// This method will convert whatever `C.Duration` is to a `Swift.Duration`.
61+
/// The default implementation uses the `run` method to trigger a job that
62+
/// does `executor.enqueue(job)`. If a particular `Clock` knows that the
63+
/// executor it has been asked to use is the same one that it will run jobs
64+
/// on, it can short-circuit this behaviour and directly use `run` with
65+
/// the original job.
5566
///
5667
/// Parameters:
5768
///
58-
/// - from duration: The `Duration` to convert
69+
/// - job: The job we wish to run
70+
/// - on executor: The executor on which we would like it to run.
71+
/// - at instant: The time at which we would like it to run.
72+
/// - tolerance: The ideal maximum delay we are willing to tolerate.
5973
///
60-
/// Returns: A `Swift.Duration` representing the equivalent duration, or
61-
/// `nil` if this function is not supported.
6274
@available(StdlibDeploymentTarget 6.2, *)
63-
func convert(from duration: Duration) -> Swift.Duration?
75+
func enqueue(_ job: consuming ExecutorJob,
76+
on executor: some Executor,
77+
at instant: Instant, tolerance: Duration?)
78+
#endif
6479

65-
/// Convert a Swift Duration to a Clock-specific Duration
80+
/// Obtain the equivalent, canonical clock, or `nil` if this clock
81+
/// is already canonical.
82+
///
83+
/// A non-canonical clock is a clock with some relationship to a base,
84+
/// canonical, clock, to which it can be converted.
85+
@available(StdlibDeploymentTarget 6.2, *)
86+
var canonicalClock: CanonicalClock? { get }
87+
88+
/// Convert an Instant to the canonical clock's equivalent Instant.
6689
///
6790
/// Parameters:
6891
///
69-
/// - from duration: The `Swift.Duration` to convert.
92+
/// - instant: The `Instant` to convert.
7093
///
71-
/// Returns: A `Duration` representing the equivalent duration, or
72-
/// `nil` if this function is not supported.
73-
@available(StdlibDeploymentTarget 6.2, *)
74-
func convert(from duration: Swift.Duration) -> Duration?
94+
/// Returns:
95+
///
96+
/// The equivalent `CanonicalClock.Instant`.
97+
func convertToCanonical(instant: Instant) -> CanonicalClock.Instant
7598

76-
/// Convert an `Instant` from some other clock's `Instant`
99+
/// Convert a Duration to the canonical clock's equivalent Duration.
77100
///
78101
/// Parameters:
79102
///
80-
/// - instant: The instant to convert.
81-
// - from clock: The clock to convert from.
103+
/// - duration: The `Duration` to convert.
104+
///
105+
/// Returns:
106+
///
107+
/// The equivalent `CanonicalClock.Duration`.
108+
func convertToCanonical(duration: Duration) -> CanonicalClock.Duration
109+
110+
/// Convert an Instant to the canonical clock's equivalent Instant.
111+
///
112+
/// Parameters:
113+
///
114+
/// - instant: The `Instant` to convert, or `nil`.
115+
///
116+
/// Returns:
117+
///
118+
/// The equivalent `CanonicalClock.Instant`, or `nil` if `instant` was `nil`.
119+
func maybeConvertToCanonical(instant: Instant?) -> CanonicalClock.Instant?
120+
121+
/// Convert a Duration to the canonical clock's equivalent Duration.
122+
///
123+
/// Parameters:
82124
///
83-
/// Returns: An `Instant` representing the equivalent instant, or
84-
/// `nil` if this function is not supported.
125+
/// - duration: The `Duration` to convert, or `nil`.
126+
///
127+
/// Returns:
128+
///
129+
/// The equivalent `CanonicalClock.Duration`, or `nil` if `duration` was `nil`.
130+
func maybeConvertToCanonical(duration: Duration?) -> CanonicalClock.Duration?
131+
}
132+
133+
extension Clock {
134+
// The default implementation works by creating a trampoline and calling
135+
// the run() method.
85136
@available(StdlibDeploymentTarget 6.2, *)
86-
func convert<OtherClock: Clock>(instant: OtherClock.Instant,
87-
from clock: OtherClock) -> Instant?
137+
public func enqueue(_ job: consuming ExecutorJob,
138+
on executor: some Executor,
139+
at instant: Instant, tolerance: Duration?) {
140+
let trampoline = job.createTrampoline(to: executor)
141+
run(trampoline, at: instant, tolerance: tolerance)
142+
}
143+
144+
// Clocks that do not implement run will fatalError() if you try to use
145+
// them with an executor that does not understand them.
146+
@available(StdlibDeploymentTarget 6.2, *)
147+
public func run(_ job: consuming ExecutorJob,
148+
at instant: Instant, tolerance: Duration?) {
149+
fatalError("\(Self.self) does not implement run(_:at:tolerance:).")
150+
}
151+
}
152+
153+
// Default implementations for canonicalization support
154+
extension Clock where CanonicalClock == Self {
155+
public var canonicalClock: CanonicalClock? { return nil }
156+
157+
public func convertToCanonical(duration: Duration) -> CanonicalClock.Duration {
158+
return duration
159+
}
160+
161+
public func convertToCanonical(instant: Instant) -> CanonicalClock.Instant {
162+
return instant
163+
}
164+
}
165+
166+
// nil-propagating versions of convertToCanonical()
167+
extension Clock {
168+
public func maybeConvertToCanonical(duration: Duration?)
169+
-> CanonicalClock.Duration?
170+
{
171+
if let duration {
172+
return convertToCanonical(duration: duration)
173+
}
174+
return nil
175+
}
176+
177+
public func maybeConvertToCanonical(instant: Instant?)
178+
-> CanonicalClock.Instant?
179+
{
180+
if let instant {
181+
return convertToCanonical(instant: instant)
182+
}
183+
return nil
184+
}
88185
}
89186

90187
@available(StdlibDeploymentTarget 5.7, *)
@@ -140,44 +237,6 @@ extension Clock {
140237
}
141238
}
142239

143-
@available(StdlibDeploymentTarget 6.2, *)
144-
extension Clock {
145-
// For compatibility, return `nil` if this is not implemented
146-
public func convert(from duration: Duration) -> Swift.Duration? {
147-
return nil
148-
}
149-
150-
public func convert(from duration: Swift.Duration) -> Duration? {
151-
return nil
152-
}
153-
154-
public func convert<OtherClock: Clock>(instant: OtherClock.Instant,
155-
from clock: OtherClock) -> Instant? {
156-
let ourNow = now
157-
let otherNow = clock.now
158-
let otherDuration = otherNow.duration(to: instant)
159-
160-
// Convert to `Swift.Duration`
161-
guard let duration = clock.convert(from: otherDuration) else {
162-
return nil
163-
}
164-
165-
// Convert from `Swift.Duration`
166-
guard let ourDuration = convert(from: duration) else {
167-
return nil
168-
}
169-
170-
return ourNow.advanced(by: ourDuration)
171-
}
172-
}
173-
174-
@available(StdlibDeploymentTarget 6.2, *)
175-
extension Clock where Duration == Swift.Duration {
176-
public func convert(from duration: Duration) -> Duration? {
177-
return duration
178-
}
179-
}
180-
181240
#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
182241
@available(StdlibDeploymentTarget 5.7, *)
183242
extension Clock {
@@ -196,44 +255,6 @@ extension Clock {
196255
}
197256
#endif
198257

199-
/// Represents traits of a particular Clock implementation.
200-
///
201-
/// Clocks may be of a number of different varieties; executors will likely
202-
/// have specific clocks that they can use to schedule jobs, and will
203-
/// therefore need to be able to convert timestamps to an appropriate clock
204-
/// when asked to enqueue a job with a delay or deadline.
205-
///
206-
/// Choosing a clock in general requires the ability to tell which of their
207-
/// clocks best matches the clock that the user is trying to specify a
208-
/// time or delay in. Executors are expected to do this on a best effort
209-
/// basis.
210-
@available(StdlibDeploymentTarget 6.2, *)
211-
public struct ClockTraits: OptionSet {
212-
public let rawValue: UInt32
213-
214-
public init(rawValue: UInt32) {
215-
self.rawValue = rawValue
216-
}
217-
218-
/// Clocks with this trait continue running while the machine is asleep.
219-
public static let continuous = ClockTraits(rawValue: 1 << 0)
220-
221-
/// Indicates that a clock's time will only ever increase.
222-
public static let monotonic = ClockTraits(rawValue: 1 << 1)
223-
224-
/// Clocks with this trait are tied to "wall time".
225-
public static let wallTime = ClockTraits(rawValue: 1 << 2)
226-
}
227-
228-
@available(StdlibDeploymentTarget 6.2, *)
229-
extension Clock {
230-
/// The traits associated with this clock instance.
231-
@available(StdlibDeploymentTarget 6.2, *)
232-
public var traits: ClockTraits {
233-
return []
234-
}
235-
}
236-
237258
enum _ClockID: Int32 {
238259
case continuous = 1
239260
case suspending = 2

stdlib/public/Concurrency/ContinuousClock.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,6 @@ extension ContinuousClock: Clock {
100100
)
101101
}
102102

103-
/// The continuous clock is continuous and monotonic
104-
@available(StdlibDeploymentTarget 6.2, *)
105-
public var traits: ClockTraits {
106-
return [.continuous, .monotonic]
107-
}
108-
109103
#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
110104
/// Suspend task execution until a given deadline within a tolerance.
111105
/// If no tolerance is specified then the system may adjust the deadline

0 commit comments

Comments
 (0)