From 942f537ff6fb07dafd69721f23a06a6760d09557 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Wed, 7 Oct 2026 11:55:12 +0000 Subject: [PATCH] reliability injector: the stale duplicate step blocks removed (the card-appears rewrite had sliced before an earlier definition); the rung is read over two seconds Co-Authored-By: Claude Fable 5.1 (cherry picked from commit 10badc02a33c559adc7ef8e1a02af13ba49c5075) --- tools/reliability/app-run.mjs | 104 +++------------------------------- 1 file changed, 8 insertions(+), 96 deletions(-) diff --git a/tools/reliability/app-run.mjs b/tools/reliability/app-run.mjs index 900660140..539cdee46 100755 --- a/tools/reliability/app-run.mjs +++ b/tools/reliability/app-run.mjs @@ -211,7 +211,14 @@ S['zero-ladder'] = async () => { const c = card(st); if (c.state === 'faulted') faultedSeen = true; if (/restarted once already/.test(c.message || '')) onceAlready = true; - if ((c.restarts || 0) > lastR) { lastR = c.restarts; marks.push({ t: Date.now(), wait: c.restart_in_s, msg: c.message }); log(`rung ${marks.length}: restart_in_s ${c.restart_in_s} "${c.message}"`); } + if ((c.restarts || 0) > lastR) { + lastR = c.restarts; + // the row's countdown as it stands over the next two seconds (the first read can land on the same tick) + let wait = c.restart_in_s || 0; + for (let k = 0; k < 4; k++) { await sleep(500); const s2 = await state(); const c2 = s2 ? card(s2) : {}; wait = Math.max(wait, c2.restart_in_s || 0); } + marks.push({ t: Date.now(), wait, msg: c.message }); + log(`rung ${marks.length}: restart_in_s ${wait} "${c.message}"`); + } await sleep(500); } setMode('ok'); @@ -284,101 +291,6 @@ S['card-appears'] = async () => { await until((st) => fakes(st).length >= 2 && fakes(st)[1].state === 'removed', 150000, 'the second card gone again'); }; -S['own-restart'] = async () => { - setMode('ok'); - const st0 = await until(mining, 300000, 'first mining'); - const pid0 = card(st0).pid, r0 = card(st0).restarts; - const tInject = Date.now(); - setMode('fast'); - const faulted = await until((st, c) => c.faults >= 1, 90000, 'the worker fault to reach the card'); - const tFault = Date.now(); - setMode('ok'); - const back = await until((st, c) => mining(st, c) && c.faults >= 1, 90000, 'mining again after the worker restart'); - const tBack = Date.now(); - await steady(15000); - const st1 = await poll(); - verdict('own-restart', [ - { ok: !!st0, what: 'the card mined with a rate above 0 on the fake worker' }, - { ok: !!faulted && /worker fault|killed by a guard/.test(card(faulted).message || ''), what: `the card showed the worker fault (${JSON.stringify(card(faulted || {}).message)})` }, - { ok: !!back, what: 'mining resumed after the miner restarted its own worker' }, - { ok: st1 && card(st1).pid === pid0 && card(st1).restarts === r0, what: `the app did not restart the miner (pid ${pid0} -> ${card(st1 || {}).pid}, app restarts ${r0} -> ${card(st1 || {}).restarts})` }, - ], { inject_to_fault_on_card: s(tFault - tInject), fault_to_mining_again: s(tBack - tFault) }); -}; - -S['zero-ladder'] = async () => { - setMode('ok'); - const st0 = await until(mining, 120000, 'mining'); - const r0 = card(st0).restarts; - const tInject = Date.now(); - setMode('zero'); - // three rungs: the gaps between consecutive watchdog restarts must grow 10, 30, 120 s (plus the 60 s rule each time) - const marks = []; - let lastR = r0; - const end = Date.now() + 600000; - let faultedSeen = false, onceAlready = false; - while (Date.now() < end && marks.length < 3) { - const st = await poll(); if (!st) break; - const c = card(st); - if (c.state === 'faulted') faultedSeen = true; - if (/restarted once already/.test(c.message || '')) onceAlready = true; - if ((c.restarts || 0) > lastR) { lastR = c.restarts; marks.push({ t: Date.now(), wait: c.restart_in_s, msg: c.message }); log(`rung ${marks.length}: restart_in_s ${c.restart_in_s} "${c.message}"`); } - await sleep(500); - } - setMode('ok'); - const back = await until(mining, 400000, 'mining again on its own after the worker is healthy'); - const tBack = Date.now(); - const waits = marks.map(m => m.wait); - const ladderOk = waits.length === 3 && waits[0] >= 8 && waits[0] <= 10 && waits[1] >= 28 && waits[1] <= 30 && waits[2] >= 118 && waits[2] <= 120; - verdict('zero-ladder', [ - { ok: marks.length === 3, what: `three watchdog restarts observed (${marks.length})` }, - { ok: ladderOk, what: `the restart delays follow the ladder 10, 30, 120 s (saw ${waits.join(', ')})` }, - { ok: marks.every(m => /hash rate 0 for 60 s/.test(m.msg || '')), what: 'the reason stayed on the card in plain words' }, - { ok: !faultedSeen && !onceAlready, what: 'no "faulted" state and no "restarted once already" words' }, - { ok: !!back, what: 'mining resumed on its own once the worker was healthy' }, - ], { zero_to_rung1: marks[0] ? s(marks[0].t - tInject) : 'n/a', rung_gaps: marks.slice(1).map((m, i) => s(m.t - marks[i].t)).join(', '), healthy_to_mining: s(tBack - (marks[2] ? marks[2].t : tInject)) }); -}; - -S['no-status'] = async () => { - setMode('ok'); - const st0 = await until(mining, 400000, 'mining'); - const pid0 = card(st0).pid, r0 = card(st0).restarts; - const tInject = Date.now(); - process.kill(pid0, 'SIGSTOP'); - log(`SIGSTOP miner pid ${pid0}`); - const rs = await until((st, c) => c.restarts > r0 || /no status line/.test(c.message || ''), 150000, 'the watchdog restart'); - const tRestart = Date.now(); - const back = await until((st, c) => mining(st, c) && c.pid !== pid0 && c.pid > 0, 400000, 'mining on the restarted miner'); - const tBack = Date.now(); - let gone = false; try { process.kill(pid0, 0); } catch { gone = true; } - if (!gone) { try { process.kill(pid0, 'SIGKILL'); } catch {} } - verdict('no-status', [ - { ok: !!rs && /no status line/.test(card(rs).message || ''), what: `the watchdog restarted the miner for missing status lines (${JSON.stringify(card(rs || {}).message)})` }, - { ok: rs && tRestart - tInject >= 85000 && tRestart - tInject <= 130000, what: `between 85 and 130 s after the miner went quiet (${s(tRestart - tInject)})` }, - { ok: !!back, what: 'mining resumed on a new miner process' }, - { ok: gone, what: 'the stopped miner process was killed' }, - ], { quiet_to_restart: s(tRestart - tInject), restart_to_mining: s(tBack - tRestart) }); -}; - -S['card-appears'] = async () => { - if (MAC) { verdict('card-appears', [{ ok: true, what: 'skipped on macOS (Apple silicon has no GPU hot-plug; the Metal path enumerates once)' }], {}); return; } - // the card leaves the enumeration (unplugged, or a problem code: the hot-plug pass marks it removed and stops its - // worker), then comes back: its worker must start with no tap - await until(mining, 300000, 'mining before the card leaves'); - setMode('absent'); - const tGone = Date.now(); - const gone = await until((st, c) => c.state === 'removed' || c.removed === true || /removed/.test(c.state || ''), 150000, 'the hot-plug pass to mark the card removed'); - const tMarked = Date.now(); - await steady(30000); - setMode('ok'); - const tAppear = Date.now(); - const back = await until(mining, 300000, 'the worker to start with no tap once the card is back'); - const tBack = Date.now(); - verdict('card-appears', [ - { ok: !!gone, what: `the hot-plug pass marked the card removed when it left (${s(tMarked - tGone)})` }, - { ok: !!back, what: 'its worker started with no tap once it was back' }, - ], { gone_to_marked: s(tMarked - tGone), back_to_mining: s(tBack - tAppear) }); -}; - S['node-silent'] = async () => { setMode('ok'); const st0 = await until(mining, 400000, 'mining');