python-trio / python-trio/trio
Figure out if we're using the correct clocks everywhere
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 7.3k
- Forks
- 431
- Avg merge
- 2d 17h
- Merged PRs (30d)
- 6
Description
Trio's standard clock is intended to be a monotonic clock that ticks only while the system is running, not while it's suspended. (Rationale: this is the kind of clock that blocking primitives like epoll/GetQueuedCompletionEvents/kqueue use for their timeout parameter, and we want times on the Trio clock to be convertable into timeouts for these calls.)
Is this true? Currently we use time.perf_counter() for our clock.
Linux
We're ok: time.perf_counter() eventually calls clock_gettime(CLOCK_MONOTONIC, ...), and on Linux, CLOCK_MONOTONIC has the properties mentioned above.
macOS
We're ok: time.perf_counter() eventually calls mach_absolute_time(), whose docs say:
Returns current value of a clock that increments monotonically in tick units (starting at an arbitrary point), this clock does not increment while the system is asleep.
(Note: on macOS, clock_gettime(CLOCK_MONOTONIC, ...) does not work like this – it keeps ticking while the system is suspended. So it's good that Python's time module doesn't use this!)
FreeBSD
I'm pretty sure we do the wrong thing: time.perf_counter() eventually ends up on pymonotonic, which ends up using CLOCK_MONOTONIC, and on FreeBSD this does keep ticking during suspend: https://www.freebsd.org/cgi/man.cgi?query=clock_gettime
Instead, we should use clock_gettime(CLOCK_UPTIME, ...).
(Also, perhaps, file a bug on Python? Not sure what the contract for time.monotonic()/time.perf_counter() is supposed to be.)
Windows
I'm pretty sure we do the wrong thing: time.perf_counter() calls QueryPerformanceCounter, and the docs say:
QueryPerformanceCounter reads the performance counter and returns the total number of ticks that have occurred since the Windows operating system was started, including the time when the machine was in a sleep state such as standby, hibernate, or connected standby.
...It is super unclear what you are supposed to do instead.
[pause to scrounge around weird corners of the internet]
OK so it sounds like the official options are:
-
QueryUnbiasedInterruptTime– but this is relatively low resolution, usually the same 15 ms resolution that we originally switched toQueryPerformanceCounterto avoid, see #33 -
QueryUnbiasedInterruptTimePrecise– exactly what we want, but the docs imply that it might be kinda slow. We should measure to find out if that's actually true. -
Use
QueryPerformanceCounter(which includes time spend suspended) plus some eldritch magic to figure out how long the system has spent suspended, so we can subtract it off again to "correct" theQueryPerformanceCounteroutput. Specifically, user-space processes can apparently find the number of 100 ns ticks that the system has spent suspended stored as an unsigned 64-bit value at memory address 0x7ffe03b0.I swear I'm not making this up.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by locating Trio's standard-clock implementation and every use of time.perf_counter(), then compare the Linux, macOS, FreeBSD, and Windows behavior described in the issue. Measure QueryUnbiasedInterruptTimePrecise against the alternatives on Windows and define done as selecting and validating a monotonic, suspend-excluding clock strategy for each platform.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- operating-systems
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100