pgserve 2.2.3 → 2.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cluster.js +64 -2
package/package.json
CHANGED
package/src/cluster.js
CHANGED
|
@@ -395,6 +395,51 @@ class ClusterRouter extends EventEmitter {
|
|
|
395
395
|
}
|
|
396
396
|
|
|
397
397
|
|
|
398
|
+
/**
|
|
399
|
+
* Build a `backendExited` handler for cluster mode supervision.
|
|
400
|
+
*
|
|
401
|
+
* On unexpected exit (`expected: false`) — postgres SIGKILL'd, OOM-killed,
|
|
402
|
+
* segfaulted, etc. — the handler:
|
|
403
|
+
* 1. logs the exit code,
|
|
404
|
+
* 2. flips `shuttingDown` so the cluster.on('exit') worker-respawn path
|
|
405
|
+
* no longer forks new workers,
|
|
406
|
+
* 3. SIGTERMs every live worker (no point routing to a dead backend), and
|
|
407
|
+
* 4. calls `exitFn(1)` so the parent process supervisor restarts us.
|
|
408
|
+
*
|
|
409
|
+
* On clean exit (`expected: true`, initiated by `pgManager.stop()`) the
|
|
410
|
+
* handler is silent — the surrounding shutdown logic handles teardown.
|
|
411
|
+
*
|
|
412
|
+
* Exported so the supervision contract can be unit-tested without spawning
|
|
413
|
+
* a real cluster (the integration path is already covered for single-process
|
|
414
|
+
* mode by tests/wrapper-supervision.test.js).
|
|
415
|
+
*
|
|
416
|
+
* @param {object} args
|
|
417
|
+
* @param {Map<number, {kill: (sig: string) => void}>} args.workers - live worker registry
|
|
418
|
+
* @param {(v: boolean) => void} args.setShuttingDown - flips outer-scope `shuttingDown`
|
|
419
|
+
* @param {(code: number) => void} [args.exitFn=process.exit] - test seam
|
|
420
|
+
* @param {(...args: unknown[]) => void} [args.log=console.error] - test seam
|
|
421
|
+
* @returns {(info: {code: number, expected: boolean}) => void}
|
|
422
|
+
*/
|
|
423
|
+
export function buildClusterSupervisionHandler({
|
|
424
|
+
workers,
|
|
425
|
+
setShuttingDown,
|
|
426
|
+
exitFn = process.exit,
|
|
427
|
+
log = console.error,
|
|
428
|
+
}) {
|
|
429
|
+
return ({ code, expected }) => {
|
|
430
|
+
if (expected) return;
|
|
431
|
+
log(
|
|
432
|
+
`[pgserve] postgres backend exited unexpectedly (code=${code}) in cluster mode; ` +
|
|
433
|
+
`the primary is exiting so a process supervisor can restart it.`
|
|
434
|
+
);
|
|
435
|
+
setShuttingDown(true);
|
|
436
|
+
for (const worker of workers.values()) {
|
|
437
|
+
try { worker.kill('SIGTERM'); } catch { /* worker may already be dead */ }
|
|
438
|
+
}
|
|
439
|
+
exitFn(1);
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
398
443
|
/**
|
|
399
444
|
* Start pgserve in cluster mode
|
|
400
445
|
*/
|
|
@@ -425,9 +470,27 @@ export async function startClusterServer(options = {}) {
|
|
|
425
470
|
console.log(`[pgserve] Embedded PostgreSQL started`);
|
|
426
471
|
console.log(`[pgserve] Socket: ${pgSocketPath || `TCP port ${pgPort}`}`);
|
|
427
472
|
|
|
473
|
+
// Track shutdown state and worker registry early so the supervision
|
|
474
|
+
// handler below can tear workers down on unexpected backend death.
|
|
475
|
+
let shuttingDown = false;
|
|
428
476
|
const workers = new Map();
|
|
429
477
|
const workerStats = new Map(); // Track stats from each worker
|
|
430
478
|
|
|
479
|
+
// Supervision: when the embedded postgres backend dies unexpectedly
|
|
480
|
+
// (SIGKILL/OOM/segfault — anything other than a clean stop()), exit the
|
|
481
|
+
// primary so a process supervisor (`genie serve`, pm2, systemd) restarts
|
|
482
|
+
// the cluster cleanly with a fresh backend. Without this, the primary
|
|
483
|
+
// keeps running with a zombie pgManager (socketDir nulled) and every
|
|
484
|
+
// worker fails StartupMessages with "Connection closed" forever while
|
|
485
|
+
// pm2 reports the process as healthy. Mirrors the single-process fix
|
|
486
|
+
// in bin/postgres-server.js (PgserveDaemon.on('backendDiedUnexpectedly'))
|
|
487
|
+
// — pgserve#45 only protected the daemon path, not cluster mode (default
|
|
488
|
+
// on multi-core systems).
|
|
489
|
+
pgManager.on('backendExited', buildClusterSupervisionHandler({
|
|
490
|
+
workers,
|
|
491
|
+
setShuttingDown: (v) => { shuttingDown = v; },
|
|
492
|
+
}));
|
|
493
|
+
|
|
431
494
|
// Fork workers with PostgreSQL connection info.
|
|
432
495
|
//
|
|
433
496
|
// Pass through using AUTOPG_<X> (the primary names) so workers don't
|
|
@@ -456,8 +519,7 @@ export async function startClusterServer(options = {}) {
|
|
|
456
519
|
workers.set(worker.id, worker);
|
|
457
520
|
}
|
|
458
521
|
|
|
459
|
-
//
|
|
460
|
-
let shuttingDown = false;
|
|
522
|
+
// (shuttingDown declared above with the supervision handler.)
|
|
461
523
|
|
|
462
524
|
// Restart dead workers (unless shutting down)
|
|
463
525
|
cluster.on('exit', (worker, code, signal) => {
|