Skip to content

Commit 87777d0

Browse files
committed
fix(db/postgres): handle idle pool client errors
1 parent c4325b8 commit 87777d0

2 files changed

Lines changed: 30 additions & 0 deletions

File tree

src/db/postgres/helper.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ const ensurePool = (): Pool => {
3232
}
3333

3434
_pool = new Pool({ connectionString });
35+
// An idle client in the pool can emit 'error' (e.g. the backend dropped the
36+
// connection). Without a listener node treats this as an uncaught exception
37+
// and crashes the process; log it instead and let the pool recycle the client.
38+
_pool.on('error', (err) => {
39+
console.error('Postgres pool error on idle client:', err);
40+
});
3541
return _pool;
3642
};
3743

test/db/postgres/helper.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest';
1919
const mockPoolQuery = vi.fn();
2020
const mockPoolEnd = vi.fn();
2121
const mockPoolCtor = vi.fn();
22+
const mockPoolOn = vi.fn();
2223

2324
vi.mock('pg', () => {
2425
class Pool {
@@ -27,6 +28,7 @@ vi.mock('pg', () => {
2728
}
2829
query = mockPoolQuery;
2930
end = mockPoolEnd;
31+
on = mockPoolOn;
3032
}
3133
return { Pool };
3234
});
@@ -114,6 +116,28 @@ describe('PostgreSQL - helper', async () => {
114116
});
115117
});
116118

119+
describe('pool error handling', () => {
120+
it('registers an idle-client error listener that logs without crashing', async () => {
121+
getDatabaseMock.mockReturnValue({
122+
type: 'postgres',
123+
enabled: true,
124+
connectionString: 'postgresql://localhost/x',
125+
});
126+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
127+
128+
await connect();
129+
130+
const errorRegistration = mockPoolOn.mock.calls.find((call) => call[0] === 'error');
131+
expect(errorRegistration).toBeDefined();
132+
133+
const handler = errorRegistration![1] as (err: Error) => void;
134+
expect(() => handler(new Error('connection terminated unexpectedly'))).not.toThrow();
135+
expect(errorSpy).toHaveBeenCalled();
136+
137+
errorSpy.mockRestore();
138+
});
139+
});
140+
117141
describe('getSessionStore', () => {
118142
it('throws when connection string is missing — no MemoryStore fallback', () => {
119143
getDatabaseMock.mockReturnValue({

0 commit comments

Comments
 (0)