Yes, let's get back to the API. I have some questions that I've been waiting to ask:
- Naming
I'd like to suggest that we rename ClockProtocol to just Clock, and WallClock to SystemClock.
I feel that these names would fit better in the standard library; see, for example RandomNumberGenerator and SystemRandomNumberGenerator. "system clock" is also more commonly used by other programming languages than "wall clock" (although the documentation for those often starts by calling it a "wall clock").
InstantProtocol could also be named TimePoint or PointInTime. That would lead us to a protocol hierarchy which says: every Clock has an associated Instant type, which is a PointInTime. Which is quite nice and intuitive, IMO.
- MonotonicClock and sleep
My understanding of the pitch is that the MonotonicClock continues to tick even while the machine is asleep and events might not be responded to. That's quite an interesting choice.
On Linux, CLOCK_MONOTONIC does not continue to tick while the system is suspended. Instead, they have a CLOCK_BOOTTIME clock for that. The reason behind it is sort of fascinating - it used to be a historical accident, and once the implementation changed in the kernel, they tried to change it so that MONOTONIC and BOOTTIME clocks would be the same.
That patch was reverted almost immediately, due to some rather predictable issues:
- systemd kills daemons on resume, after >WatchdogSec seconds
of suspending (Genki Sky). [Verified that that's because systemd uses
CLOCK_MONOTONIC and expects it to not include the suspend time.]
- MATE desktop dims the display and starts the screensaver right after
system resume (Pavel).
The Go programming language tried to make a similar change (having timers continue to tick on sleep), and an engineering lead on the Windows team asked them to revert that on Windows:
The bigger question to me is whether the change that introduced this regression was the right one at all. The Windows kernel team changed timer behavior in Windows 8 to stop advancing relative timeouts on wake. Otherwise when you open your laptop lid, every timer in the system goes off all at once and you get a bunch of unpredictable errors. Software is generally written to assume that local processes will make forward progress over reasonable time periods, and if they don't then something is wrong. When the machine is asleep, this assumption is violated. By making relative timers behave like threads, so that they both run together or they both don't, the illusion is maintained. You can claim these programs are buggy, but they obviously exist. Watchdog timers are well-known constructs.
This was a conscious design decision in Windows, and so it's disappointing to see the Go runtime second guess this several years later in a bug fix.
As far as behavior on Linux, there is clearly no consensus in issue #24595, which discusses this same problem. And indeed you can see that the CLOCK_MONOTONIC/CLOCK_BOOTTIME convergence was reverted in the kernel exactly because of the reason we stopped advancing time in Windows: random code has random failures due to timeouts.
Ultimately, Go abandoned making the change from MONOTONIC -> BOOTIME on Linux, and (after a really long and somewhat heated discussion) the corresponding patch for Windows was reverted as well. This post summarises the differences quite nicely:
-
"Real time", aka CLOCK_BOOTTIME on Linux or "interrupt time" on Windows. This measures the passage of real time and continues to pass when the system is suspended. This clock has meaning in the external world, such as to users and across networks and distributed systems.
-
"Program time", aka CLOCK_MONOTONIC on POSIX or "unbiased interrupt time" on Windows. This also measures the passage of real time, but pauses when the system is suspended and no programs can make progress. This clock is more meaningful internal to a system.
I think it is worth examining this area of the proposal more closely. This is certainly a delicate issue, and the issues described by both the Linux kernel contributors and Microsoft kernel team make sense to me.
I'm not sure which side of the fence I sit on here, but I think it's worth asking: why are we choosing that the default MonotonicClock in Swift should continue to tick during system sleep? Should we perhaps change that, or should we offer both?