mirror of
https://github.com/stablyai/orca.git
synced 2026-10-27 08:02:51 +00:00
pg-pool removes its own `error` listener when it hands a client out (pg-pool@3.14.0 index.js:344) and only reattaches it in `_release` (index.js:385). Between acquire and release the client therefore has no `error` listener, so when Cloud SQL terminates that session mid-statement the emit becomes an unhandled 'error' event and the process exits. `absorbPostgresIdleClientErrors` cannot see it: pg-pool routes to `pool.on('error')` only from the idle listener. Attach a per-checkout `error` listener in the one seam every relay checkout passes through, log a single warn line, and release the client with the error so pg-pool destroys it instead of pooling a dead connection. The listener is removed on release so it cannot accumulate. The in-flight query still rejects, so existing failure reporting and the transaction retry ladder are unchanged. Claude-Session: https://claude.ai/session/ced32ebb-7155-4413-adad-1eccd14c2010
51 lines
1.5 KiB
TypeScript
51 lines
1.5 KiB
TypeScript
import { EventEmitter } from 'node:events'
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
import { PostgresPoolPressure } from './postgres-pool-pressure.js'
|
|
|
|
describe('PostgreSQL pool pressure', () => {
|
|
it('reports current waiters and interval high-water marks', async () => {
|
|
let now = 1_000
|
|
let resolveConnection!: (client: unknown) => void
|
|
const connection = new Promise((resolve) => {
|
|
resolveConnection = resolve
|
|
})
|
|
const pool = {
|
|
totalCount: 3,
|
|
idleCount: 0,
|
|
waitingCount: 0,
|
|
connect: vi.fn(() => {
|
|
pool.waitingCount++
|
|
return connection
|
|
})
|
|
}
|
|
const pressure = new PostgresPoolPressure(pool as never, () => now)
|
|
const pending = pressure.connect()
|
|
now = 1_750
|
|
|
|
expect(pressure.consumeCounts()).toMatchObject({
|
|
databasePoolTotal: 3,
|
|
databasePoolIdle: 0,
|
|
databasePoolWaiting: 1,
|
|
databasePoolWaitersMax: 1,
|
|
databasePoolOldestWaitMs: 750,
|
|
databasePoolWaitMsMax: 750
|
|
})
|
|
|
|
now = 2_250
|
|
pool.waitingCount--
|
|
// An EventEmitter because the acquire path now attaches an `error` listener.
|
|
resolveConnection(Object.assign(new EventEmitter(), { query: vi.fn(), release: vi.fn() }))
|
|
await pending
|
|
expect(pressure.consumeCounts()).toMatchObject({
|
|
databasePoolWaiting: 0,
|
|
databasePoolWaitersMax: 1,
|
|
databasePoolOldestWaitMs: 0,
|
|
databasePoolWaitMsMax: 1_250
|
|
})
|
|
expect(pressure.consumeCounts()).toMatchObject({
|
|
databasePoolWaitersMax: 0,
|
|
databasePoolWaitMsMax: 0
|
|
})
|
|
})
|
|
})
|