mirror of
https://github.com/RhapsodiOS/RhapsodiOS.git
synced 2026-11-03 22:01:40 -07:00
[GH-ISSUE #46] kernel: setitimer and select timeouts fire up to two ticks early (hzto truncates, timeout() counts from the last tick) #38
Labels
No labels
bug
documentation
enhancement
enhancement
good first issue
help wanted
help wanted
pull-request
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
github-starred/RhapsodiOS#38
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @raynorpat on GitHub (Oct 14, 2026).
Original GitHub issue: https://github.com/RhapsodiOS/RhapsodiOS/issues/46
Symptom
setitimer(ITIMER_REAL)andselect()timeouts fire before the requested interval has passed, by 7-16 ms (up to about two 10 ms ticks). POSIX requires these timers never to expire early.Seen with the stress suite's
stress-misc timer(ffs-perf branch) on the v2 image's kernel, i386 under QEMU TCG. It failed in 6 of 7 single-worker seeds, for bothsetitimerandselect:The test takes its start time before arming and its end stamp in the SIGALRM handler, or right after
selectreturns. It allows 1 ms of slack and ignores negative intervals, which are #40's backward clock steps. Signal latency can only make a timer look late, so these are real early expiries.Cause (from source)
Two roundings, both in the early direction:
hzto()truncates (bsd/kern/kern_clock.c:391-418):ticks = (ms) / (tick / 1000). An 86 ms request becomes 8 ticks (80 ms).time, the BSD clock updated once per tick, not from the current time. So the remaining interval is computed against a stale "now".timeout()counts from the last tick, not from now (kern_clock.c:347-368):ticks_to_tvalspec(ticks)plustick_stamp, the time of the most recent clock tick.Together, an interval can end up to about 2 ticks (20 ms at HZ=100) early. 86 ms became 8 ticks counted from a stale tick, so 70-80 ms; 79 ms was observed.
Callers that inherit this:
setitimer/getitimer(kern_time.c,realitexpire);selecttimeouts (sys_generic.c);nanosleep/sleep, and anything else that goes throughhzto+timeout/tsleep.Suggested fix
hzto: round up instead of truncating:(ms + tickms - 1) / tickms. Then add one tick, the classic BSD rule (FreeBSD'stvtohzdoes "+1 because we don't know where in the current tick we are"), so a timeout never fires before the full interval. Compute "now" withmicrotime()rather than the per-ticktime.timeout(): base the deadline on the current counter (clock_get_counter(System)) instead oftick_stamp, so no partial tick is lost.Regression test:
stress-misc timershould PASS on every seed once fixed. Its 1 ms tolerance may be tighter than tick granularity allows for late wakeups; it only checks early ones.Related: #40 (gettimeofday backward steps), #44.
@raynorpat commented on GitHub (Oct 15, 2026):
Fixed in ffs-perf
3b6598be1(kernel: round hzto() up and add a tick so setitimer and select timeouts never fire early).hzto()now rounds the interval up to whole ticks at microsecond precision and adds one tick, becausetimeout()counts from the last clock tick (the FreeBSDtvtohzrule). A deadline that is now or past still returns 0, sowaitevent's poll path is unchanged. It keeps measuring fromtime: every caller builds its deadline astime + interval, so the stale base cancels out, and switching onlyhztotomicrotime()would shorten timers.Tested with an A/B on a v2 snapshot guest (v2-kernel + the patch vs the stock v2 kernel):
stress-misc -n 1 timerfailed 10/10 seeds, including 1791980622 and 1791981966 (what=early, itimer and select)stress-miscrun passes every category