// Robust pg.Client cleanup for SSE-routes that hold a long-running LISTEN- // connection. Without this helper, `pgClient.end()` can hang silently when // the underlying socket is in a weird state (Fast Refresh, abrupt browser // close, Vercel function recycle), leaving the connection in 'idle' on the // Postgres server. After ~10-20 reconnects the Neon connection-pool fills // up and new SSE-connections fail with ERR_INCOMPLETE_CHUNKED_ENCODING. // // Strategy: race `pgClient.end()` against a short timeout; if the timeout // wins, force-destroy the underlying socket so the OS releases the FD and // Neon notices the disconnect. import type { Client } from 'pg' const END_TIMEOUT_MS = 2_000 interface PgClientWithStream { connection?: { stream?: { destroy?: (err?: Error) => void } } } export async function closePgClientSafely( client: Client, label: string, ): Promise { // Drop notification/error handlers so a late event from the dying // connection cannot trigger downstream cleanup again. client.removeAllListeners('notification') client.removeAllListeners('error') client.on('error', () => { // Swallow: connection is being torn down on purpose. }) let timer: ReturnType | null = null const timeout = new Promise<'timeout'>((resolve) => { timer = setTimeout(() => resolve('timeout'), END_TIMEOUT_MS) }) const result = await Promise.race([ client.end().then(() => 'ended' as const), timeout, ]).catch(() => 'error' as const) if (timer) clearTimeout(timer) if (result !== 'ended') { if (process.env.NODE_ENV !== 'production') { console.warn(`[${label}] pgClient.end() did not finish in ${END_TIMEOUT_MS}ms — forcing socket destroy`) } const stream = (client as unknown as PgClientWithStream).connection?.stream try { stream?.destroy?.(new Error(`forced socket destroy from ${label}`)) } catch { // best-effort } } }