mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-16 07:13:12 -06:00
The checkpointer startup line no longer describes a policy it stopped having
It still read "escalate >16MB or 3 growing runs" after #240 added a size floor and a cooldown to that second rule. Growth-across-three-runs on its own is exactly the half that no longer holds, so the line described a checkpointer that does not exist — and it is the line an operator reads to learn the policy. During an incident it would send you hunting for a blocking checkpoint that the new gates had in fact suppressed. [wal-checkpoint] off-thread checkpointer started (PASSIVE every 15000ms; blocking TRUNCATE when the WAL exceeds 16MB, or after 3 growing runs but only at >=8MB and at most once per 300s; respawn max 5/60000ms) A test now asserts the line reports every knob that governs the decision, since nothing else keeps a log string and the rule it describes in step. Writing it caught its own bug first: anchoring the slice back to `return worker;` matched the idempotence guard at the top of startWalCheckpointer(), not the log below it, so the window was empty and every assertion passed vacuously. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014skWYXJUWhF73EvNPgB2AS
This commit is contained in:
parent
70b2227fa8
commit
bf9ad16486
|
|
@ -121,7 +121,17 @@ function startWalCheckpointer(db, dbPath) {
|
||||||
try { db.pragma('wal_checkpoint(TRUNCATE)'); } catch (_) { /* best-effort */ }
|
try { db.pragma('wal_checkpoint(TRUNCATE)'); } catch (_) { /* best-effort */ }
|
||||||
|
|
||||||
worker = spawnWorker();
|
worker = spawnWorker();
|
||||||
console.log(`[wal-checkpoint] off-thread checkpointer started (every ${config.walCheckpointIntervalMs}ms; escalate >${config.walCheckpointHighWaterMB}MB or ${config.walCheckpointStarvationRuns} growing runs; respawn max ${config.walCheckpointRespawnMax}/${config.walCheckpointRespawnWindowMs}ms)`);
|
// #240: this line is where an operator learns the escalation policy, so it must state ALL of
|
||||||
|
// it. It advertised only "3 growing runs" after the size floor and the cooldown were added,
|
||||||
|
// which is the half that no longer holds on its own — and reading it during an incident would
|
||||||
|
// send you looking for a checkpoint that the gates had in fact suppressed.
|
||||||
|
console.log(
|
||||||
|
`[wal-checkpoint] off-thread checkpointer started (PASSIVE every ${config.walCheckpointIntervalMs}ms; ` +
|
||||||
|
`blocking TRUNCATE when the WAL exceeds ${config.walCheckpointHighWaterMB}MB, ` +
|
||||||
|
`or after ${config.walCheckpointStarvationRuns} growing runs but only at >=${config.walCheckpointStarvationFloorMB}MB ` +
|
||||||
|
`and at most once per ${Math.round(config.walCheckpointEscalateCooldownMs / 1000)}s; ` +
|
||||||
|
`respawn max ${config.walCheckpointRespawnMax}/${config.walCheckpointRespawnWindowMs}ms)`
|
||||||
|
);
|
||||||
return worker;
|
return worker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,28 @@ test('#240: the worker actually applies the floor it is handed', () => {
|
||||||
// Measured, not assumed: with a single reader mid-transaction, TRUNCATE returns busy=1
|
// Measured, not assumed: with a single reader mid-transaction, TRUNCATE returns busy=1
|
||||||
// after sitting on its 5s busy timeout and reclaims nothing (probe: WAL 8.8MB -> 8.8MB,
|
// after sitting on its 5s busy timeout and reclaims nothing (probe: WAL 8.8MB -> 8.8MB,
|
||||||
// worst main-thread write 4,936ms). That outcome must not be logged as a success.
|
// worst main-thread write 4,936ms). That outcome must not be logged as a success.
|
||||||
|
// The startup line is where an operator learns the policy. It went stale the moment the gates were
|
||||||
|
// added — it still promised "3 growing runs" with no mention of the floor or the cooldown, so it
|
||||||
|
// described a checkpointer that no longer existed. A log line that states a rule has to state the
|
||||||
|
// whole rule, and nothing but a test keeps the two in step.
|
||||||
|
test('#240: the startup line states the WHOLE escalation policy', () => {
|
||||||
|
const ctl = fs.readFileSync(path.join(__dirname, '..', 'db', 'wal-checkpointer.js'), 'utf8');
|
||||||
|
// Anchor forward from the message text, not back to `return worker;` — the idempotence guard at
|
||||||
|
// the top of startWalCheckpointer() returns first, so slicing to it yields nothing at all.
|
||||||
|
const start = ctl.indexOf('off-thread checkpointer started');
|
||||||
|
assert.ok(start > 0, 'the startup line is gone entirely');
|
||||||
|
const line = ctl.slice(start, start + 800);
|
||||||
|
for (const knob of [
|
||||||
|
'walCheckpointIntervalMs',
|
||||||
|
'walCheckpointHighWaterMB',
|
||||||
|
'walCheckpointStarvationRuns',
|
||||||
|
'walCheckpointStarvationFloorMB',
|
||||||
|
'walCheckpointEscalateCooldownMs',
|
||||||
|
]) {
|
||||||
|
assert.ok(line.includes(knob), `the startup line must report ${knob} — an operator reads it as the policy`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('#240: a TRUNCATE that reclaimed nothing says so', () => {
|
test('#240: a TRUNCATE that reclaimed nothing says so', () => {
|
||||||
const src = fs.readFileSync(path.join(__dirname, '..', 'db', 'wal-checkpointer-worker.js'), 'utf8');
|
const src = fs.readFileSync(path.join(__dirname, '..', 'db', 'wal-checkpointer-worker.js'), 'utf8');
|
||||||
assert.match(src, /busy === 1/, 'worker must inspect the checkpoint result');
|
assert.match(src, /busy === 1/, 'worker must inspect the checkpoint result');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue