A lost idle Postgres connection no longer kills Heimdall
Soul pass 3 (R2): the pool had no 'error' listener. pg emits an idle client's connection loss there (a database restart, a terminated backend), so the event went unheard, the process died, and Node printed the pg Client with its connection parameters. createPostgresStore now listens and writes one line naming the error code. The pool has already dropped the dead client and connects again on the next query. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0f5ffd9e69
commit
c626a86d55
2 changed files with 39 additions and 1 deletions
|
|
@ -800,5 +800,17 @@ export function createPostgresStore(databaseUrl: string): PostgresStore {
|
|||
const pool = new Pool({
|
||||
connectionString: databaseUrl,
|
||||
});
|
||||
// An idle client whose connection ends (a database restart, an
|
||||
// administrator terminating the backend) is emitted here. Unheard, the
|
||||
// event kills the process and Node prints the pg Client, connection
|
||||
// parameters included. The pool has already dropped the dead client and
|
||||
// opens a new one on the next query, so this only says what happened, by
|
||||
// code.
|
||||
pool.on("error", (error) => {
|
||||
const code = (error as { code?: unknown }).code;
|
||||
console.error(
|
||||
`Heimdall lost an idle Postgres connection (${typeof code === "string" ? code : "error"}); the pool reconnects on the next query.`
|
||||
);
|
||||
});
|
||||
return new PostgresStore(pool);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { newDb } from "pg-mem";
|
||||
import { PostgresStore } from "../src/store/postgres.js";
|
||||
import { createPostgresStore, PostgresStore } from "../src/store/postgres.js";
|
||||
|
||||
describe("PostgresStore", () => {
|
||||
it("round-trips core auth records through postgres", async () => {
|
||||
|
|
@ -183,3 +183,29 @@ describe("PostgresStore", () => {
|
|||
await store.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe("createPostgresStore", () => {
|
||||
// pg emits an idle client's connection loss on the pool. With no listener
|
||||
// that event kills the process, and Node prints the pg Client with its
|
||||
// connection parameters.
|
||||
it("survives an idle connection's loss and says so by code alone", async () => {
|
||||
const store = createPostgresStore("postgres://heimdall:CANARYpw@127.0.0.1:1/heimdall");
|
||||
const pool = (store as unknown as { pool: { emit(event: string, ...args: unknown[]): boolean } }).pool;
|
||||
const logged: string[] = [];
|
||||
const originalError = console.error;
|
||||
console.error = (...args: unknown[]) => void logged.push(args.map(String).join(" "));
|
||||
try {
|
||||
const terminated = Object.assign(new Error("terminating connection due to administrator command CANARY"), {
|
||||
code: "57P01",
|
||||
client: { connectionParameters: { password: "CANARYpw" } },
|
||||
});
|
||||
expect(() => pool.emit("error", terminated, terminated.client)).not.toThrow();
|
||||
} finally {
|
||||
console.error = originalError;
|
||||
await store.close();
|
||||
}
|
||||
expect(logged).toEqual([
|
||||
"Heimdall lost an idle Postgres connection (57P01); the pool reconnects on the next query.",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue