mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-26 18:31:34 +01:00
fix(server): make the null-socket guard registration idempotent
registerPostgresNullSocketGuard() added one uncaughtException listener on every call. A caller that runs it many times in one process, such as a test suite that calls startServer() repeatedly, crossed Node's default listener-count warning threshold. Skip the process.on call when the guard's own listener function is already registered. Also extend the unrelated-crash fixture scenario to assert the child process reports the error message and the stack, and add a test that proves a second registration adds no second listener. Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2 files changed
+19
-2
No files matched your search
@@ -152,6 +152,13 @@ describe("registerPostgresNullSocketGuard", () => {
|
||||
registerPostgresNullSocketGuard({ POSTGRES_NULL_SOCKET_GUARD_ENABLED: "false" });
|
||||
expect(process.listenerCount("uncaughtException")).toBe(before);
|
||||
});
|
||||
|
||||
it("adds one listener however many times it runs", () => {
|
||||
const before = process.listenerCount("uncaughtException");
|
||||
registerPostgresNullSocketGuard({});
|
||||
registerPostgresNullSocketGuard({});
|
||||
expect(process.listenerCount("uncaughtException")).toBe(before + 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("handlePostgresNullSocketGuardException", () => {
|
||||
@@ -257,10 +264,16 @@ describe("the driver crash sequence, reproduced against a fake wire server", ()
|
||||
expect(result.code).toBe(0);
|
||||
}, 20_000);
|
||||
|
||||
it("a non-matching uncaught exception still ends the process", async () => {
|
||||
it("a non-matching uncaught exception still ends the process and reports its cause", async () => {
|
||||
const result = await runFixture("unrelated-crash");
|
||||
|
||||
expect(messageTypes(result)).not.toContain("unexpected-survival");
|
||||
expect(result.code).toBe(1);
|
||||
// The logger writes the fatal record to standard output; Node's own
|
||||
// default handler would have used standard error, but the guard
|
||||
// replaces that handler for every uncaught exception, so this is the
|
||||
// only place the message and the stack can appear.
|
||||
expect(result.stdout).toContain("an unrelated failure the guard must not swallow");
|
||||
expect(result.stdout).toContain("at Immediate");
|
||||
}, 20_000);
|
||||
});
|
||||
@@ -97,9 +97,13 @@ export function handlePostgresNullSocketGuardException(error: unknown): void {
|
||||
/**
|
||||
* Registers the guard's `uncaughtException` listener, unless an operator
|
||||
* disables it with `POSTGRES_NULL_SOCKET_GUARD_ENABLED=false` (or `0`).
|
||||
* Call this once, early, from the server entry point.
|
||||
* Call this once, early, from the server entry point. This function is
|
||||
* idempotent: a repeat call adds no second listener, so a caller that runs
|
||||
* many times in one process (a test suite that calls `startServer()`
|
||||
* repeatedly) never exceeds Node's default listener-count warning.
|
||||
*/
|
||||
export function registerPostgresNullSocketGuard(env: NodeJS.ProcessEnv = process.env): void {
|
||||
if (!isGuardEnabled(env)) return;
|
||||
if (process.listeners("uncaughtException").includes(handlePostgresNullSocketGuardException)) return;
|
||||
process.on("uncaughtException", handlePostgresNullSocketGuardException);
|
||||
}
|
||||
Reference in new issue
Block a user