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:
ScreenTinker 2026-08-06 22:12:01 -05:00
parent 70b2227fa8
commit bf9ad16486
2 changed files with 33 additions and 1 deletions

View file

@ -121,7 +121,17 @@ function startWalCheckpointer(db, dbPath) {
try { db.pragma('wal_checkpoint(TRUNCATE)'); } catch (_) { /* best-effort */ }
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;
}

View file

@ -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
// 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.
// 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', () => {
const src = fs.readFileSync(path.join(__dirname, '..', 'db', 'wal-checkpointer-worker.js'), 'utf8');
assert.match(src, /busy === 1/, 'worker must inspect the checkpoint result');