I'm working with OS-level UDP packet timestamps in FreeBSD. In general this is a feature (SO_TIMESTAMP) in some operating systems that lets you access the time that the OS received a packet -- as opposed to when your application called recv() for it -- as a control message on that packet. It comes in as either a Timespec or a Timeval depending on your configuration. On Linux, the time value comes from CLOCK_REALTIME, and you can construct a SystemTime from this by converting it to a duration and adding it to SystemTime::UNIX_EPOCH.
FreeBSD has a specific socket feature (SO_TS_MONOTONIC) that lets you get these sockets from CLOCK_MONOTONIC, which, on FreeBSD, is the same clock that Instant is sourced from. It would be useful to be able to create an actual Instant from this Timespec, given that the underlying Instant on FreeBSD is also a Timespec from the same clock.
There's a really dreadful and somewhat dubious way to do this by creating a mem::zeroed Instant and adding a Duration to it, but it would be nice if instead there was an OS-specific extension that let you create an Instant from a Timespec. Is this worth proposing more formally?
SystemTime comes from CLOCK_REALTIME, whereas Instant comes from CLOCK_MONOTONIC, so using SystemTime would be inconsistent. You could make two arbitrary SystemTime values but they would be incompatible with SystemTime::now(), whereas the CLOCK_MONOTONIC values from the receipt timestamp are compatible with Instant::now() on FreeBSD (and it would be convenient to be able to use that with them). It's also idiomatic for some libraries to take Instants to represent monotonic clock values, which would make sense to use in this case.
In this case, I think just exposing an Instant::ZERO on operating systems where the semantics make sense (Linux/FreeBSD both just use CLOCK_MONOTONIC's semantics for example) would work here. That way you could convert a Timespec to a Duration and offset the Instant::ZERO by it.
OTOH yes, providing this as functionality on the socket would be great, but it's rather complicated to couple the creation of the Instant with the reading of the timespec from the socket. It would be great if I could have had the nix crate could create an Instant here for example, but it's nontrivial. It could be something that socket2 could do, but would be a pretty significant undertaking to add the cmsghdr support and such there.
Hm. I still think Instant::MIN would be more sensical and might be possible to generate universally, but I suppose it wouldn't be guaranteed to be ZERO.
Instant::CURRENT_EPOCH or similar? A reminder that it may not be the same from run to run on every OS, even though its representation will in practice be zero.
Nothing precludes adding both! Picking up on the discussion we had earlier, I said that Instant::MIN would be useful as a default value, for example if you're tracking ACK timestamps and want to record the latest, you could use MIN as a starting point. That said, Option works fine for Default, because you can always do latest = cmp::max(latest, Some(Instant::now())); for this. It also niche optimizes to the same size as Instant (right now on Linux, anyways).
Instant::ZERO is a different use case, not really for default, but rather to allow OS-based timestamps to better integrate with libraries that deeply integrate Instant as their timestamping mechanism. Instant::IDENTITY could also work, with the following property:
a: Duration > b: Duration iff Instant::IDENTITY + a > Instant::IDENTITY + b.
You could do this at startup by fixing your own Instant as a global, but IDENTITY could be const.
Yeah, exactly. That would allow you to create your own stream of Instant timestamps and be compatible with libraries that take use Instant (as long as they don't call Instant::now() themselves) like quinn-proto. You can use libc yourself to sample the clock you want, convert that to a Duration, add it to the Instant::IDENTITY (saving a syscall), and you have a custom Instant.
I think it's worth proposing the additive identity part as a libs ACP with two options: we can either namespace this to std::os or we can put this in std::time and just hope that all Instant reprs we choose in the future have an additive identity.
I think adding such a constraint isn't unreasonable, for note, despite what I said in our conversation before. I just wanted to be clear that, until we promise such, assuming that std types use a particular repr has historically turned out badly.
It’s not an “additive identity”, there is no type you can add Instant to that “won’t change the original value”. It could be 0 or 1000 or -2,000,000 and all of the existing operations on Instant and Duration would remain consistent. The property being asked for here is a promise that Instant::now() - Instant::THE_NEW_THING is meaningful in some timebase, and then document what timebase it is. (The current docs for Instance explicitly say “we might change how this is implemented on any OS in future releases”.)
EDIT: Another way to phrase this is that the interesting operation is Instant::from_timespec_with_assumed_same_base_as_Instant(ts), but then someone could pass in a zero timespec and find out the base anyway.
It would be possible to have os specific extension functions like "from_monotonic", then you could still change the actual implementation in the future (as long as you can internally in std compute an offset between the two time bases).
This is what I actually want -- essentially an constructor for Instant, but I understand that that's a tough sell that's been discussed and shot down before.
At the very least I think framing it as Instant::IDENTITY rather than Instant::ZERO sidesteps the issues that arose when discussing creating a constructor for it. There's no guarantee that creating an Instant::IDENTITY and adding a Duration to it gives it a specific internal value, unlike the expectation for Instant::ZERO. You can create Instant::IDENTITY now by sticking Instant::now() in a static lazy lock. The only real advantage is that Instant::IDENTITY could be const. That's considerably weaker than what I'm looking for, but it could at least be a useful thing I might propose.
Instant::MIN might be slightly better for being const, having the identity property, and also being <= all other Instants, which might be an additional property that someone finds useful.
How does this work with NICs that support hardware timestamping? Or other OSes? It seems you would need a cross platform abstraction for thos functionality in general, if they work differently.
It's specifically for OSes that allow you to get OS-level timestamps from CLOCK_MONOTONIC, which to my knowledge is FreeBSD and I believe MacOS. I don't think there's a general solution here, hence the OS-Specific caveat above.
The thing is, if std doesn’t guarantee it uses the same timebase as Instant::now, you lose the type-checking benefits. Instant now represents multiple clocks: std’s, and “relative to this constant”*. Two Instant values can no longer be ordered with certainty.
I realize it’s annoying, but in similar projects in the past my teams have used our own RelativeInstant or similar that merely wraps Duration. That’s not very satisfying, but I’d prefer it to having two kinds of Instant floating around, which is what this becomes if we’re still unwilling to commit to a timebase for Instant.
* which, again, is not an “identity” in any mathematical sense.
I don’t see a distinction between that and promising a timebase, because I can put 0 into that function and get a timebase. It doesn’t matter what timebase Instant is using internally; it behaves as-if it’s using the timebase from_monotonic understands.
I don't think it's straightforward to convert between clocks, since they all run under different rules depending on the OS. CLOCK_BOOTTIME follows different rules from CLOCK_MONOTONIC follows different rules from CLOCK_RUNTIME. Because of that, it would be hard to guarantee that a future change to Instant would still be able to be created from an arbitrary CLOCK_MONOTONIC sample.
I personally think it would be reasonable for the std to commit to Instant == CLOCK_MONOTONIC for certain OSes because they have the same purpose, but I guess they want to leave room for major OS changes some time from now.