GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||
GeezerVison is showing github.com — Fix timers firing up to 1ms early by pctablet505 · Pull Request #755 · MagicStack/uvloop Conversation
|
loop->time at start |
due at | real time slept | |
|---|---|---|---|
| before | 1000 (floored) | 1000 + 100 = 1100 | 99.1 ms ❌ |
| after | 1000 (floored) | 1000 + 100 + 1 = 1101 | 100.1 ms ✅ |
Only the value handed to libuv changes (self.timeout + 1). self.timeout, and therefore TimerHandle.when() / get_when(), still reports the requested deadline. The trade-off is that a timer can fire up to ~1 ms later than before, which is within libuv's 1 ms resolution.
This is a different truncation from #233, which was fixed by rounding the float-to-ms conversion in call_at(). That path is unchanged.
test_call_later_not_early runs asyncio.sleep(0.01) 300 times and asserts that each one lasts at least the delay, measured with time.monotonic(). The existing test_call_later_rounding (#233) can't catch this, because it measures with the loop's own millisecond-floored clock.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
libuv resamples its internal clock as the floor of the current monotonic time truncated to whole milliseconds, then schedules a timer for loop->time + timeout. Since the fractional millisecond already elapsed at the moment of sampling is discarded, the timer can fire up to ~1ms before the requested delay has actually passed. This made asyncio.sleep(n) occasionally return slightly under n, as reported in MagicStack#739. Pad the timeout handed to uv_timer_start() by 1ms to compensate, while leaving the reported deadline (get_when()/TimerHandle.when()) as the originally requested value.
66f1983 to
f2b3a0d
Compare
…modules of asyncio libuv counts a timer in whole milliseconds of a clock that it rounds down, so a timer of uvloop was due up to a millisecond early by time.monotonic, which failed the tests of Pyjo.Steps of Pyjoyment now and then. _Loop.call_later rounds the delay up, as MagicStack/uvloop#755 does. event_loop_modules stays the one of Pyjo.Reactor.Asyncio: the loop of uvloop makes no frames of Python below a callback (#7). Co-Authored-By: Claude Opus 5.5 <[email protected]>
libuv counts a timer in whole milliseconds from uv_now, its clock rounded down, and Winloop rounds the delay to them, as uvloop does, so a timer, as asyncio.sleep, was due up to a millisecond before its time, where the loop of asyncio is never early: a sleep of 2 ms took 1.5 ms. call_later of _Loop, which call_at of Winloop calls too, rounds the delay up to a millisecond and adds the one that the clock loses, as _Loop of Pyjo.Reactor.Uvloop does, after MagicStack/uvloop#755. The subtest of Pyjo-Reactor-Uvloop measures with time.perf_counter, which time.monotonic is on Windows since Python 3.13, and which counted in the ticks of the system of 15.6 ms before. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ime, and a timer not due before its time (#6) * Pyjo.Reactor.Winloop: a socket that loops watch, polled by one at a time libuv of Windows polls a socket with an exclusive request of AFD, which ends the request of any other loop for the same socket, also of a copy of it, and that loop polls it again, in a busy loop of C with no event at all. The workers of Pyjo.Server.Prethread watch the listen sockets of the manager on Windows, the same sockets in every thread: four idle loops took 240% of the processors and starved the process and the tests next to it, with or without the GIL. _Loop keeps the readers and the writers of each socket, and a registry of the process has the loops that watch it, the owner that libuv polls it for, and the loops that run and wait for it. The owner hands it over to the next one after each of its events, so that the connections go to every worker in turn, and at the end of its run; a loop changes the polls of libuv only in its thread. A socket that one loop watches goes to libuv as before. Closes #5 Co-Authored-By: Claude Opus 5.5 <[email protected]> * uv.lock: Pyjoyment of Git main at 1fc984a Pyjoyment of Git main with pyjoyment/Pyjoyment#550 and pyjoyment/Pyjoyment#552, the tests of a page of GitHub without its title in the tutorial and of the servers of Hot and Reload that did not answer or did not stop. Co-Authored-By: Claude Opus 5.5 <[email protected]> * uv.lock: Pyjoyment of Git main at 7b09754 Pyjoyment of Git main with pyjoyment/Pyjoyment#553, a sleep of Pyjo.Steps in its tests on an event loop of libuv, whose timer may be due a millisecond early, and pyjoyment/Pyjoyment#554, the data of a connection that the daemon has removed discarded. Co-Authored-By: Claude Opus 5.5 <[email protected]> * Pyjo.Reactor.Winloop: a timer not due before its time libuv counts a timer in whole milliseconds from uv_now, its clock rounded down, and Winloop rounds the delay to them, as uvloop does, so a timer, as asyncio.sleep, was due up to a millisecond before its time, where the loop of asyncio is never early: a sleep of 2 ms took 1.5 ms. call_later of _Loop, which call_at of Winloop calls too, rounds the delay up to a millisecond and adds the one that the clock loses, as _Loop of Pyjo.Reactor.Uvloop does, after MagicStack/uvloop#755. The subtest of Pyjo-Reactor-Uvloop measures with time.perf_counter, which time.monotonic is on Windows since Python 3.13, and which counted in the ticks of the system of 15.6 ms before. Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]>
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
uvloop may sleep less thann for asyncio.sleep(n)
There was an error while loading. Please reload this page.