MCPcopy Create free account
hub / github.com/colbymchenry/codegraph / WalCheckpointValve

Class WalCheckpointValve

src/db/wal-valve.ts:80–290  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

78}
79
80export class WalCheckpointValve {
81 private timer: ReturnType<typeof setInterval> | null = null;
82 private inflight: Promise<void> | null = null;
83 /** Writer pause in progress (hard cap breached): passes loop until a full backfill. */
84 private pause: Promise<void> | null = null;
85 /**
86 * WAL file size observed when a checkpoint last reported the ENTIRE WAL
87 * backfilled. Growth is measured against this baseline — see the header
88 * comment for why absolute size cannot be used.
89 */
90 private sizeAtLastFullBackfill = 0;
91 private readonly softBytes: number;
92 private readonly hardBytes: number;
93 private readonly fileCapBytes: number;
94
95 /**
96 * Futility latch: consecutive backfill give-ups (a reader pinning the WAL)
97 * disable further writer pauses for a cooldown, so a pinned phase degrades
98 * to the pre-valve behavior (unbounded WAL, folded when the pinner exits)
99 * instead of burning a 20-pass checkpoint attempt — each pass a worker
100 * thread + fresh connection — at EVERY over-cap boundary. That churn is
101 * what turned a pinned kernel-scale resolution from slow into OOM-killed
102 * (§7a.1 run 1: 22GB WAL, exit 137 at an envelope the pre-fix build
103 * survived).
104 */
105 private consecutiveGiveUps = 0;
106 private futileUntil = 0;
107
108 constructor(
109 private readonly db: DatabaseConnection,
110 softMb: number = resolveWalValveMb(process.env.CODEGRAPH_WAL_VALVE_MB),
111 private readonly intervalMs: number = CHECK_INTERVAL_MS,
112 log: (msg: string) => void = () => {}
113 ) {
114 this.softBytes = softMb * 1024 * 1024;
115 this.hardBytes = this.softBytes * HARD_CAP_MULTIPLIER;
116 this.fileCapBytes = this.softBytes * FILE_CAP_MULTIPLIER;
117 // CODEGRAPH_WAL_VALVE_DEBUG=1 surfaces valve decisions to stderr without
118 // needing the caller's verbose plumbing — the observability gap that let
119 // §7a.1 run 1 fail silently (give-ups were verbose-gated and invisible).
120 this.log = process.env.CODEGRAPH_WAL_VALVE_DEBUG
121 ? (m) => console.error(`[wal-valve] ${m}`)
122 : log;
123 }
124
125 private readonly log: (msg: string) => void;
126
127 private mb(n: number): string {
128 return `${Math.round(n / 1024 / 1024)}MB`;
129 }
130
131 /** Un-backfilled growth estimate: bytes the WAL has grown past the last full backfill. */
132 private growthBytes(): number {
133 return this.db.getWalSizeBytes() - this.sizeAtLastFullBackfill;
134 }
135
136 /** Begin watching the WAL. Idempotent; the timer never holds the loop open. */
137 start(): void {

Callers

nothing calls this directly

Calls

no outgoing calls

Tested by

no test coverage detected