µPitch - SuspendingClock and ContinuousClock epochs

The proposal for Clock, Instant and Duration brought in two primary clock types: SuspendingClock and ContinuousClock. These both have a concept of a reference point for their Instant types.

Not all clocks have a starting point, however in these cases they do. Generally, it cannot required for a clock's instant definition to have a start or the start may not be a fixed point. However it can be useful that a given instant can be constructed to determine the elapsed duration from the starting point of that clock if it does have it.

Two new properties will be added, one to SuspendingClock and another to ContinuousClock. Both of these properties will be the epoch for which all Instant types are derived from; practically speaking this is the "zero" point for these clocks.

extension ContinousClock {
    public var epoch: Instant { get }
}

extension SuspendingClock {
    public var epoch: Instant { get }
}

These can be used to gather information like for example the uptime of a system, or the active time of a system;

let clock = ContinousClock()
let uptime = clock.now - clock.epoch

Or likewise;

let clock = SuspendingClock()
let activeTime = clock.now - clock.epoch

It was considered to add a constructor or static member to the SuspendingClock.Instant and ContinousClock.Instant however the home on the clock itself provides a more discoverable and nameable location.

This is a purely additive change and provides no direct impact to existing ABI. It only carries the ABI impact of new properties being added to an existing type.

It is suggested that this be used as an informal protocol for other clocks. It was considered as an additional protocol but that was ultimately rejected because no generic function made much sense that would not be better served with generic specialization or explicit clock parameter types.

4 Likes

Ooo, how back deployable would this be? Alamofire is using a "restricted" API for system uptime I'd really like to replace.

3 Likes

That has yet to be determined how that will be done, but they are very simple... so it is not horribly complicated to do so - that is an implementor discretion thing so id rather focus on the usefulness, functionality and naming rather than getting into the weeds with that part just yet.

If it were up to me 100%; I would not be opposed to the idea of back deploying the back to the introduction of the clocks. But it needs some additional (out of this uPitch discussion first).

3 Likes

It's strange to me that this proposal says that these clocks have well-defined epochs but doesn't actually document what those epochs are. The examples imply that the epoch is based on the system boot time; is that a portable guarantee?

3 Likes

Could have sworn I'd commented before but yes, please add this API! We currently do something gnarly in Swift Testing to simulate the epoch.

The epoch definitions are zero's. This is portable to all cases where the clocks are implemented (other system implementations may be different I guess... but honestly im not sure how anyone would define the system epoch any differently).

The current proposal says that these clocks have meaningful starting times, but then it doesn't document anything about what that starting time means. That seems hard to use without making extra assumptions! For all the user knows, this is measured relative to the process launch or to January 1st, 1970.

It sounds like the intent is that this matches the system epoch used by CLOCK_MONOTONIC and similar system timing facilities. Among other things, that makes it a useful way of measuring time across processes / system logs. That seems like something the proposal should document (maybe with a note that typical systems align that with the boot time), and maybe it should also inform the property name.

If there's a system that doesn't have a meaningful system-wide epoch, it's probably better to not provide this API there than to provide it with radically different semantics.

1 Like

See also a similar discussion on the Rust side, where they (originally) very explicitly decided not to make their SuspendingClock equivalent have a semantic epoch.

1 Like

Correct; this should NOT be applied to any other clocks that do not have a stable or defined.
I definitely think that adding documentation to this effect is reasonable and warranted. The name was chosen because this is to be the point of starting measuring. Which for example the pitch for the UTCClock does have a non-zero instant as it's epoch. But measuring the differential between this gives something different of a use - for the suspending and continuous cases (both derived system types from similar timing to monotonic clocks) the resultant of the difference between the now and the epoch is the active uptime and the total uptime respectively. Whereas the UTCClock case is not a boot point but instead a reference for which all time is derived; which is more useful for the cases of serialization and conversion to foreign systems. In the UTCClock case that would share the same value across backing implementations; all UTCClock.epoch values on all platforms would return Jan 1 2001 since that is how we implement the underlying storage of Date.

From my admittedly limited understanding, the differential here is that ContinuousClock.Instant by construction cannot be compared to SuspendingClock.Instant whereas Rust has a singular Instant type shared for all clocks. From a Swift perspective you may be able to get a shared duration differential between two instants from two clocks, but instead of the types being congruent you have to do some hoop jumping (which to be honest is possible today... but is rather distasteful and gives questionable answers).

I would be amenable to restricting all future platform implementations to have a reference epoch for the suspending and continuous clocks as a Duration 0 and adjust the implementation of the now for those platforms to be referent to that. But since those types are relatively opaque (not allowing direct access to their members) it does not seem to have the practical costs of the type ambiguity that Rust seems to suffer here.

In short; if someone subtracts two SuspendingClock.Instants, extracts the components, converts to fractional seconds and adds that value to a Date; that is programmer error.

Okay, documenting that it matches other platform-defined concepts of the epoch on specific platforms sounds good to me.

I guess my remaining question is whether "epoch" alone is such a clear term of art in this domain that it's a reasonable property name. It certainly doesn't seem like there's any value in having a consistent property name across these clocks and UTCClock.

So the only other prior art other than epochs is perhaps something inspired by Date's referenceDate - using modern Swift naming it might be instead called reference but that is a bit confusing to other terminology.

Would systemEpoch be clearer, if there really is a documentable assumption that this corresponds to some system-wide value and not just something internal to Swift?

2 Likes

I like that—it strongly implies the epoch is system-specific and not universal.

Bikeshedding:

  • currentEpoch
  • bootEpoch
  • localEpoch
  • machineEpoch
  • transientEpoch

I'm ok with systemEpoch since that is also applicable to the UTCClock too.

1 Like