Skip to content

Commit 188c43c

Browse files
authored
Merge pull request #1583 from dcoric/feat/postgres-pool-error-handler
fix: handle idle pool client errors in the PostgreSQL sink
2 parents 74aedea + 44de8a2 commit 188c43c

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
@@ -33,6 +33,12 @@ const ensurePool = (): Pool => {
3333
}
3434

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

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
const mockPoolConnect = vi.fn();
2324
const mockClientQuery = vi.fn();
2425
const mockClientRelease = vi.fn();
@@ -30,6 +31,7 @@ vi.mock('pg', () => {
3031
}
3132
query = mockPoolQuery;
3233
end = mockPoolEnd;
34+
on = mockPoolOn;
3335
connect = mockPoolConnect;
3436
}
3537
return { Pool };
@@ -125,6 +127,28 @@ describe('PostgreSQL - helper', async () => {
125127
});
126128
});
127129

130+
describe('pool error handling', () => {
131+
it('registers an idle-client error listener that logs without crashing', async () => {
132+
getDatabaseMock.mockReturnValue({
133+
type: 'postgres',
134+
enabled: true,
135+
connectionString: 'postgresql://localhost/x',
136+
});
137+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
138+
139+
await connect();
140+
141+
const errorRegistration = mockPoolOn.mock.calls.find((call) => call[0] === 'error');
142+
expect(errorRegistration).toBeDefined();
143+
144+
const handler = errorRegistration![1] as (err: Error) => void;
145+
expect(() => handler(new Error('connection terminated unexpectedly'))).not.toThrow();
146+
expect(errorSpy).toHaveBeenCalled();
147+
148+
errorSpy.mockRestore();
149+
});
150+
});
151+
128152
describe('getSessionStore', () => {
129153
it('throws when connection string is missing — no MemoryStore fallback', () => {
130154
getDatabaseMock.mockReturnValue({

0 commit comments

Comments
 (0)