OverviewFilesCommitsIssuesDocsReleasesPeers

keet-daemon commits / 305dfce

design: rt scheduler port done (bare-timers batching, held-throw rule)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
305dfce9dca004a859eec20884f203e7cd03da6d
by plankatron <fleeky@gmail.com> · 2026-10-04T21:58:37.000Z

Changed files (1) +30 -0

docs/CPP-PORT-DESIGN.md +30 -0
……@@ -3197,6 +3197,36 @@ injected per layer:
31973197 - **Alternative:** keep per-callback checkpoints and document the divergence; no ported
31983198 library is known to depend on it (the timer audit).
31993199 - The r1 drill's ONCE-first-iteration mutant survives until then (11/13).
3200- **DECIDED 2026-10-04 (user): port the scheduler. DONE.**
3201 - **rt Base = bare-timers 3.2.0's Scheduler, ported:** Task state bits, the expiry/id heap,
3202 `_timeout` (delay < 1 → 1), `_refresh` (re-arms even a fired timeout), `_clear`, `_ref` /
3203 `_unref` refcount, and batched `_ontimeout` / `_onimmediate` (abort-on-throw).
3204 - **Natives (binding.c) behind 4 calls:** timeout, immediate, ref, unref.
3205 - Real: one `uv_timer`, plus a check + idle pair that stops itself before the batch.
3206 - Virtual: libuv's loop as a script (cached loop time, at most one timers pass per
3207 iteration, a poll that jumps to the timer).
3208 - **Held microtask throws, re-measured:** probes pr1–pr7, 10–30 runs each, some under CPU load.
3209 R1's "end of the iteration" was imprecise. The rule is: delivered at the END of the first
3210 check phase that BEGINS after the throw. A throw inside an immediates batch waits for the
3211 next check phase, so timers libuv runs in between come first. rt keeps a permanent, older,
3212 unref'd check handle that runs last in each phase (libjs's env check has the same position).
3213 - **NOT compared:** an unref'd immediate queued in the last ref'd timer's callback. Bare runs it
3214 only because libjs keeps its V8-platform async handle REF'd while V8 tasks are outstanding
3215 (libjs `update()`). strace shows the same extra polls after an empty timer, so this is V8 task
3216 timing, not a timer rule.
3217 - **Gate traps fixed:**
3218 - Same-ms timers race `Date.now()` ticks under load. Batch scripts spin past the boundary,
3219 measured 30/30 with 8 CPU spinners.
3220 - The gates now run a generator-written manifest, not a `*.json` glob (a reused scratch dir
3221 kept dropped scripts).
3222 - **Gates:** rt gate with 6 batch + 4 held-throw + ioCheckpoint scripts, 3/3 under load; ss
3223 traces, interop 15/15 and ctest 69/69 all on the new scheduler.
3224 - **Drills:**
3225 - r1 re-authored for the scheduler: 20/20 red, each killed by its intended script.
3226 Dropped as equivalent: ONCE-first (with the first-iteration idle, ONCE never sleeps) and
3227 virtual uncached loop time (expiries are JS `Date.now()`).
3228 - r2 is 2/4 plus 2 equivalent: a one-shot keepalive re-arms itself, because every keepalive
3229 write calls `refresh()`; a dropped TimeoutRefresh clears in its destructor.
32003230- **R3:** mux / replicator / Core onto `rt` (Clock, `onsettimeout`, `Host::defer`,
32013231 `resumeVia_` removed). All repl / store / core gates and every drill re-run.
32023232- **R4:** storage, per the decision above.