Files
orca/cloud/apps/relay/src/postgres-pool-pressure.test.ts
T
Jinwoo Hong eb6068a434 fix(relay): stop a terminated checked-out PostgreSQL client from killing the cell (#21840)
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
2026-09-20 17:46:25 -04:00

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
})
})
})