@@ -139,7 +139,7 @@ The fundamental entry point for working with deadlines is a single function: `wi
139139/// - Returns: The result of the operation if it completes successfully before or after the deadline expires.
140140///
141141/// - Throws: The error thrown by the operation
142- nonisolated(nonsending) public func withDeadline <Return : ~ Copyable , Failure : Error , C : Clock >(
142+ nonisolated(nonsending) public func withDeadline <Return : ~ Copyable , Failure : Error , C : Clock & Identifiable >(
143143 _ expiration : C.Instant,
144144 tolerance : C.Instant.Duration? = nil ,
145145 clock : C = ContinuousClock (),
@@ -206,7 +206,7 @@ Constructing an instant every time is not per se the most terse; so a simple ext
206206with the same compositional advantage as the primary entry point.
207207
208208``` swift
209- nonisolated(nonsending) public func withDeadline <Return : ~ Copyable , Failure : Error , C : Clock >(
209+ nonisolated(nonsending) public func withDeadline <Return : ~ Copyable , Failure : Error , C : Clock & Identifiable >(
210210 in timeout : C.Instant.Duration,
211211 tolerance : C.Instant.Duration? = nil ,
212212 clock : C = ContinuousClock (),
@@ -358,71 +358,92 @@ This error is propagated from whenever the task (or child task) is cancelled via
358358The reason specified will then be available to the ` CancellationError ` and can be retrieved from the ` reason `
359359property on the cancellation error.
360360
361- #### Current deadlines and Clock
361+ #### Accessing active deadlines
362362
363- The deadline cannot be expressed as an existential since that would not be easily usable. So
364- to access the current applied deadline a common accessor would need to be used.
365- This most generally can be expressed as an earliest ContinuousClock instant representing
366- the deadline. This will also resolve a long standing pain point for clocks for the conversion
367- between reasonable time-bases.
368-
369- To accomplish this a new required method will be added to the ` Clock ` protocol and
370- a default implementation will then also be provided such that existing implementations
371- wont break but will have a sensible default that can be customized to something more
372- appropriate.
363+ External systems may need to interoperate with active deadlines. This means that the
364+ applied deadline needs to be retrievable, however this particularly becomes
365+ tricky since the clock is generic for the deadlines. To that end
366+ the accessor for the active deadline is generic for a clock type
373367
374368``` swift
375- public protocol Clock <Duration>: Sendable {
376- associatedtype Duration
377- associatedtype Instant : InstantProtocol where Instant.Duration == Duration
378-
379- var now: Instant { get }
380- var minimumResolution: Instant.Duration { get }
381-
382- func sleep (until deadline : Instant, tolerance : Instant.Duration? ) async throws
369+ extension Task where Success == Never , Failure == Never {
370+ public static var hasActiveDeadline: Bool { get }
383371
384- // New addition:
385- func earliestContinuousInstant (from instant : Instant) -> ContinousClock.Instant
372+ public static func activeDeadline <C : Clock & Identifiable >(for clock : C) -> C.Instant?
386373}
374+ ```
387375
388- extension Clock {
389- public func earliestContinuousInstant (from instant : Instant) -> ContinuousClock.Instant { .now }
390- }
376+ If any deadline is active then the static property ` hasActiveDeadline ` returns true.
377+ This only applies to the current task if the execution of that task is within a call
378+ to ` withDeadline ` . This allows for determining the return of the ` deadline ` static
379+ function to be used to know if a known clock has a value being applied as a current
380+ deadline. This does mean that the usage must be aware of the potential clocks being
381+ used. This is however a requirement since to use the deadline itself the clock must
382+ be known for any sort of usage to an external system.
391383
392- extension ContinuousClock {
393- public func earliestContinuousInstant (from instant : Instant) -> ContinuousClock.Instant { instant }
394- }
395384
396- extension SuspendingClock {
397- public func earliestContinuousInstant (from instant : Instant) -> ContinuousClock.Instant {
398- let delta = instant - .now
399- return ContinousClock.now + delta
385+ ``` swift
386+ if Task.hasActiveDeadline {
387+ if let deadline = Task.activeDeadline (for : ContinuousClock ()) {
388+ // use the deadline as a ContinuousClock.Instant
389+ }
390+ if let deadline = Task.activeDeadline (for : SuspendingClock ()) {
391+ // use the deadline as a SuspendingClock.Instant
400392 }
401393}
402394```
403395
404- This will mean that ContinousClock and SuspendingClock will gain new implementations
405- of this which will then return the earliest instant represented in reference to the
406- continuous clock.
407-
408- This all then means that the deadline APIs can use that implementation to vend
409- out a earliest continuous deadline, alleviating the issues around existential ` InstantProtocol `
410- types and providing a reasonable frame of reference for expiration.
396+ When the call to ` activeDeadline(for:) ` is made, the query looks up the most narrow
397+ application of any specified deadline with that clock, if the current nesting of
398+ ` withDeadline ` calls does not used the specified clock type then the next nesting
399+ up the call stack is used.
400+
401+ If the nesting of ` withDeadline ` is stacked with a ` ContinuousClock ` deadline of
402+ "in two seconds" and then a ` SuspendingClock ` of "in three seconds" and a new
403+ nesting is made of a ` ContinuousClock ` is made for "in 10 seconds" the last
404+ 10 seconds is known to be less narrow than the outer 2 seconds continuous clock
405+ deadline. This means that within the scope of the "in 10 seconds" deadline the query
406+ for the ` activeDeadline(for: ContinuousClock.self) ` would return the deadline of within
407+ 2 seconds and the ` activeDeadline(for: SuspendingClock.self) ` would return the deadline
408+ of within 3 seconds. Since clock instants cannot be compared without potentially
409+ arbitrarily lossy conversions it means that the query for the current applied deadline
410+ is only accurate to the specific clock type given.
411+
412+ #### Identification of Clocks for coalescing
413+
414+ The expected behavior when setting a deadline is that any active deadline, given a specific clock,
415+ will always apply with the most narrow deadline available. Specifically if a deadline is
416+ active for an expiration of ` in 10 seconds ` and a new deadline is applied for ` in 5 seconds `
417+ relative both relative to the continuous clock, then the applied deadline within the new
418+ scope is the ` in 5 seconds ` . Likewise if the reverse was applied; where it is already at ` in 5 seconds `
419+ and a new scope is applied to ` in 10 seconds ` both on the continuous clock, then the internal
420+ logic will effectively skip the ` in 10 seconds ` since that deadline is known to beyond the current
421+ active deadline. This must have some way of determining if a given clock passed in to
422+ the ` withDeadline ` functions is that same specific clock. To that end, the clocks are
423+ required to be identified. The two major clocks; ` ContinuousClock ` and ` SuspendingClock `
424+ both will gain a new conformance to ` Identifiable ` and each of which will have a new ID
425+ type of ` SystemClockID ` . As a side effect this means that new APIs can be written as:
426+ ` where C: Clock & Identifiable, C.ID == SystemClockID ` . That particular refinement not only
427+ allows for the direct identification of the specific clock but also a constraint to the
428+ standard system clocks.
429+
430+ Note: since the system clock may grow additional identifiers it is left as non-exhaustive.
411431
412432``` swift
413- extension Task where Success == Never , Failure == Never {
414- public static var currentEarliestContinuousDeadline: ContinuousClock.Instant? { get }
433+ @nonexhaustive
434+ public enum SystemClockID : Hashable {
435+ case continuous
436+ case suspending
415437}
416- ```
417438
418- This accessor does not preclude any potential more rich type information for deadlines
419- and clocks being added later, but provides a straightforward interface for interoperation
420- with external systems. The Task deadline can only be reasonable vended to the current task
421- since that value would be only transient upon a given deadline - therefore accessing it
422- externally would be rather prone to race conditions. The per task lookup will traverse
423- the nested deadlines and calculate the earliest of all of the applied deadlines that would
424- fire per the ContinuousClock. By doing so this leaves the least inaccuracy of measurement
425- (and control by custom clocks as well) to translate to existing external systems.
439+ extension ContinuousClock : Identifiable {
440+ public var id: SystemClockID { . continuous }
441+ }
442+
443+ extension SuspendingClock : Identifiable {
444+ public var id: SystemClockID { . suspending }
445+ }
446+ ```
426447
427448### Behavioral Details
428449
@@ -795,6 +816,8 @@ nesting. This approach was not taken because:
795816 explicit ` withDeadline ` API would remain useful even if such a mechanism were added.
796817
797818## Changelog
819+ - 1.2 Revisde for feedback
820+ - Added accessors to add a way to access the active deadlines
798821- 1.1 Returned for revision
799822 - The typed throws signature was altered to avoid an extra error type
800823 - Removed the restriction around the instant requiring the duration type to be ` Swift.Duration `
0 commit comments