| … | … | @@ -3197,6 +3197,36 @@ injected per layer: |
| 3197 | 3197 | - **Alternative:** keep per-callback checkpoints and document the divergence; no ported |
| 3198 | 3198 | library is known to depend on it (the timer audit). |
| 3199 | 3199 | - 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. |
| 3200 | 3230 | - **R3:** mux / replicator / Core onto `rt` (Clock, `onsettimeout`, `Host::defer`, |
| 3201 | 3231 | `resumeVia_` removed). All repl / store / core gates and every drill re-run. |
| 3202 | 3232 | - **R4:** storage, per the decision above. |