In the discussion of RFC0014 it became apparent that the Time::Span type can lead to confusion. Its primary purpose is to represent a duration of time. But we also use it for a reading of the monotonic clock (Time.monotonic). This makes some sense because the monotonic clock is just the time elapsed since some arbitrary point in time.
But this double use leads to problems when specifying some kind of timeout or delay: A duration represents a relative distance from the current instant. A monotonic clock reading represents an absolute instant. Either semantic is valid. And the type system makes it impossible to tell them apart.
The first proposal for a monotonic clock was actually for Clock.monotonic (#3826), but somehow - after intricate discussions about involving Time and whether monotonic readings are even necessary on their own - we ended up piggy-bagging Time.monotonic on Time::Span (#5108). I believe it was mostly for the sake of simplicity?
Maybe it’s time to rethink that? Having a separate type to represent a monotonic clock reading would be very helpful for use cases such as Fiber.timeout: A Time::Span argument represents a relative duration, a Time::Monotonic (working title) argument represents an absolute instant.
The only affected stdlib API would be Time.monotonic. It would be relatively easy to deprecate that and replace it with, for example Time::Monotonic.now, or Time::Clock.now.
Clocks might be interesting to have. We could have clock-aware sleeps and timeouts, like wait until 5 pm (real time) or wait until 10 monotonic seconds have passed.
I’d actually just consider a type for a clock reading, i.e. a representation for an instant in the monotonic clock’s timeline.
There’s no need for a dedicated type for a monotonic clock. We just need a single class method to get the current instant. That’s it.
Throwing together monotonic time and non-monotonic wall time does not sound like a good idea. At least none that I would like to pursue. Impossible to get right. They’re different clocks for a reason.
Oh sorry, I wasn’t suggesting we add a dedicated type for diff kinds of clocks. But instead having it not return Time::Span could make it so it’s easier to resolve Time.montonic to a wallclock time if so desired (instead of the silly nanosecond math stuff I have to do currenlty); maybe via some #to_* method on the new return type.
I’ve never inspected the value of the return value of Time.monotonic until right this moment. I knew it was a Time::Span but always assumed it was the duration since the process started. I honestly had no idea what the values were.
I think Time::Instant is a better name and could be used instead of Time::Monotonic, you know it’s a good name when you use it to describe what it means.
I like the argument. But I fear this name would be too imprecise and create a new ambiguity: Time also represents an instant, just on a different timeline.
Time::MonotonicInstant would be sufficiently specific, but I figure it could just be shortened to Time::Monotonic.
Perhaps Time::Tick could also be an option. But I find the name Monotonic most suitable because it resembles the existing Time.monotonic method.
Having the result of 10.seconds and Time.monotonic be separate types is a very good idea; it’s something that has caused me accidental confusion at times. (Not because I misunderstood, but because I was sleepy and it compiled, so I assumed I hadn’t made any major mistakes… it took me a while to find it!)
One could make a struct Time::Monotonic that was just a light wrapper around a Time::Span solely to keep the compiler from letting the user mix them without explicitly casting it… that could greatly reduce the tendency of the barrel of the gun to point at our feet, while not being very complex. Then the docs would just need to explain succinctly the reason for it to be a separate thing, which at least at a surface level wouldn’t need to be very wordy.
every time I think of timers for loops I always go back to this blog post. Timeouts and cancellation for humans — njs blog . I think we should abstract timeout and other reasons to cancel into a cancellation token - there are other reasons to abort async code besides timeouts
That’s a great resource. I’m familiar with Go’s context module, which implements the same thing.
This might be more relevant input for RFC 0014, though?
The documentation for Time states that it is for “incremental time”, and then links to this definition… which then very plainly states that “incremental time” is monotonic. I would think either the docs need to be changed if Time is not monotonic, or else Time::Instant just seems like duplicated functionality/another name for Time.
The linked reference refers to a concept of time, where incrementally means a progression of monotonically increasing units from a specific point.
I see how this could be misinterpreted as describing a monotonic clock. But it only describes properties of the timeline.
That document does not even talk about specific clock sources, which is more of an implementation detail compared to the discussed user facing concepts.
It’s an unfortunate reality that terms related to time keeping are not always precisely defined. There is serious and confusing term overloading
We noticed that very drastically while discussing RFC 15 that sometimes different sources apply entirely opposite semantics.
Perhaps we need to find a better definition to link to, or write our own glossary for time related terms (RFC 15 already has a starter)