| … | … | @@ -3229,6 +3229,66 @@ injected per layer: |
| 3229 | 3229 | write calls `refresh()`; a dropped TimeoutRefresh clears in its destructor. |
| 3230 | 3230 | - **R3:** mux / replicator / Core onto `rt` (Clock, `onsettimeout`, `Host::defer`, |
| 3231 | 3231 | `resumeVia_` removed). All repl / store / core gates and every drill re-run. |
| 3232 | - **R3 DESIGN (2026-10-04).** |
| 3233 | - **Seams removed:** |
| 3234 | - `repl::Clock` and its eight test/tool clocks (Test, Loop, Still, Null, Virtual, Fake…); |
| 3235 | - `Core::onsettimeout`; |
| 3236 | - `mux::Host::defer` and `Protomux::defer`; |
| 3237 | - `Replicator::resumeVia_`. |
| 3238 | - **Libraries call `rt` exactly where the JS calls the runtime:** |
| 3239 | - replicator: request timeout `setTimeout(onrequesttimeout, ms)`; `await backoff()` = |
| 3240 | `setTimeout(resolve, sleep)`; `_notDownloadingTimer` = `setTimeout` + `unref()`; |
| 3241 | `Date.now()` → `rt::now()`; |
| 3242 | - session-state recovery: `await setTimeout(5000 / 250)` → `rt::setTimeout` (JS awaits it |
| 3243 | in session-state itself, not through the replicator); |
| 3244 | - corestore gc: `setInterval(gcCycle, 2000).unref()`. protomux-wakeup gc: `setInterval`, |
| 3245 | as JS. speedometer: the module's `setInterval(inc, 250).unref()` tick counter, ported |
| 3246 | as is. hyperblobs monitor: `Date.now()`; |
| 3247 | - protomux: `queueTick` → `rt::queueMicrotask`; `Promise.resolve` → a microtask; |
| 3248 | safety-catch's async rethrow → a real throw in a microtask (rt's held-throw rule); |
| 3249 | - replicator waiters (`resumeVia_`) and autobee-wakeup (`muxer->defer`) → |
| 3250 | `rt::queueMicrotask`. |
| 3251 | - **Drivers.** A shared `tools/rt-harness.h` provides: |
| 3252 | - `act(fn)` — `rt::callback`; |
| 3253 | - `runUntil(ms, pred)`; |
| 3254 | - `ChildWatch` — `uv_poll` on a child's stdout, listener and frame socket, each event |
| 3255 | entering through `rt::callback`. |
| 3256 | |
| 3257 | Who moves where: |
| 3258 | - pipe tests (m3c, m4c, wakeup ×2, blobs monitor) and the keetd-replicate / keetd-store |
| 3259 | tools → rt REAL; |
| 3260 | - trace tools (core-ops, store-ops, blobs-ops, bee-read, m4d appender) → rt VIRTUAL. |
| 3261 | store-ops' timed ops (`gc` waits N s; a 300 ms ready race) become loop waits. |
| 3262 | - **Order, each step gated:** |
| 3263 | - **R3a:** drivers onto rt behind ADAPTERS (a Clock over rt, `defer` = `rt::queueMicrotask`). |
| 3264 | Gate: ctest. |
| 3265 | - **R3b:** seams removed, faithful timers as above. Gate: ctest plus every m3c-era drill |
| 3266 | (m5g1*, m5g2*, m5g3e3, m5g4e1/e2, m5f*). |
| 3267 | - **R3c:** what rt newly makes portable: `RandomIterator` (`rt::random` = `Math.random`) |
| 3268 | and `queueUpdateAll`'s ≥ 40-peer path with `_updateAllBump` |
| 3269 | (`setTimeout(updateAll, getUpdateAllDelay)`). Gate designed when R3b is done. |
| 3270 | - **NOT R3 (R4):** every `await` — `seeker.update`, storage, and `yieldToLoop` (it needs the |
| 3271 | same continuation-style `_updateNonPrimary`). |
| 3272 | - **R3a DONE 2026-10-04** (keetd 4cf2283). Every driver runs on rt; ctest 69/69. |
| 3273 | - **Pipe tests:** the old poll-loop body became the event step. m3c went from 182 s to 91 s. |
| 3274 | - **Trace tools:** on the virtual runtime. |
| 3275 | - **keetd-replicate / keetd-store:** rt is stepped on the transport's loop thread until 4e-4. |
| 3276 | M3d G1 passes, the same as the baseline. |
| 3277 | - `rt::runOnce()` added. |
| 3278 | - **Speedometer, ported ahead of R3b:** the module's `setInterval(inc, 250).unref()` with |
| 3279 | `setIntervalOverride`. |
| 3280 | - **The blobs-monitor gate's ticks are the script's (user decision):** |
| 3281 | - why: its speeds came from wall-clock 250 ms ticks across processes. The old driver passed |
| 3282 | unloaded only because its per-step overrun kept events off tick boundaries; it failed 5/8 |
| 3283 | under load. Aligning the script to an exact schedule failed 8/8 (events land ON the |
| 3284 | boundaries). Both the JS peer and keetd capture the one setInterval, and the driver advances |
| 3285 | `ms / 250` ticks once every side is quiet; |
| 3286 | - result: 16/16, including under load; |
| 3287 | - **Harness trap:** never close an fd a uv_poll watches. Child::onAccept / onRelease let go of |
| 3288 | the fd first; a step's `read_frames` → `try_accept` closed a polled listener, and libuv |
| 3289 | aborted with EEXIST. |
| 3290 | - **Pre-existing, measured:** under heavy load s19 sometimes misses the JS uploader's "received" |
| 3291 | report (old 3/10, new 4/10; the JS-only control fails too). |
| 3232 | 3292 | - **R4:** storage, per the decision above. |
| 3233 | 3293 | - **Then 4e-4** (`createProtocolStream` / `replicate`): the first gate that composes the stack |
| 3234 | 3294 | is a COMPOSED trace against JS (secret-stream + protomux + replicator), now meaningful. |