Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions packages/server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import http from 'node:http';
import { createRequire } from 'node:module';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import v8 from 'node:v8';
import { getRequestListener } from '@hono/node-server';
import { Hono } from 'hono';
import { closeDbSingleton, probeListenNotify } from './db/client';
Expand Down Expand Up @@ -213,6 +214,50 @@ async function main() {
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));

// SIGUSR2 → V8 heap snapshot. Off by default because snapshots contain
// in-memory secrets (DB URL, OAuth tokens, secret-proxy cache) and
// workers spawn as the same Linux UID. Operator opts in by setting
// ALLOW_HEAP_SNAPSHOT=1 on the pod, sends `kubectl exec ... kill -USR2 1`,
// copies the file out, then unsets the env / rolls the pod.
//
// Blocks the event loop for ~seconds (proportional to heap size) and
// requires ~heap-size extra memory while writing. Don't trigger from a
// pod close to the cgroup limit or it will OOM mid-snapshot. Trigger
// also blocks /health/ready (DB SELECT 1) — drain via Service first if
// multi-replica.
//
// Single-flight + fixed filename: subsequent signals while a snapshot is
// in progress are ignored, and the path is `/tmp/lobu.heapsnapshot`
// (overwritten each time) so a stuck-on flag can't fill the tmpfs.
if (process.env.ALLOW_HEAP_SNAPSHOT === '1') {
const SNAPSHOT_PATH = '/tmp/lobu.heapsnapshot';
let inProgress = false;
process.on('SIGUSR2', () => {
if (inProgress) {
logger.warn('[heap] SIGUSR2 ignored — snapshot already in progress');
return;
}
inProgress = true;
logger.warn(
{ path: SNAPSHOT_PATH },
'[heap] SIGUSR2 received — writing heap snapshot (blocks event loop)'
);
try {
v8.writeHeapSnapshot(SNAPSHOT_PATH);
logger.warn({ path: SNAPSHOT_PATH }, '[heap] snapshot written');
} catch (err) {
logger.error({ err }, '[heap] writeHeapSnapshot failed');
} finally {
inProgress = false;
}
});
logger.warn(
'[heap] ALLOW_HEAP_SNAPSHOT=1 — SIGUSR2 will write heap dumps to ' +
SNAPSHOT_PATH +
'. Unset and roll the pod when done; snapshots contain secrets.'
);
}

// Start HTTP server
logger.info({ port }, 'Starting server');

Expand Down
Loading