From f4940120f90b30d4e940a6390e3a3a2c7c43fe33 Mon Sep 17 00:00:00 2001 From: zfaustk <4340287+zfaustk@users.noreply.github.com> Date: Thu, 13 Aug 2026 01:11:02 +0800 Subject: [PATCH] fix: add default error listeners ## Context - Principle: Client and Pool background errors should reach explicit observers without becoming uncaught exceptions by default. - Why: Unobserved EventEmitter error events can terminate the process even after operation-level errors have already been returned. ## Key Deltas - Error emitters: Client, native Client, and Pool had no default handler -> each instance absorbs only otherwise-unobserved error events; why: preserve process stability while retaining normal user listeners. Key refs: packages/pg/lib/client.js:53, packages/pg/lib/native/client.js:25, packages/pg-pool/index.js:69 - Regression coverage: error behavior was implicit -> public emit behavior covers both no-listener and user-listener cases; why: prove the same Error still reaches user code. Key refs: packages/pg/test/unit/client/configuration-tests.js:13, packages/pg/test/native/evented-api-tests.js:9, packages/pg-pool/test/events.js:10 - Pool guidance: documentation warned of an uncaught exception -> guidance explains the default listener and recommends explicit observation; why: align the documented contract with pg@9 behavior. Key refs: docs/pages/apis/pool.mdx:264, packages/pg-pool/README.md:242 ## Verification Result: passed --- docs/pages/apis/pool.mdx | 8 ++++---- packages/pg-pool/README.md | 5 ++++- packages/pg-pool/index.js | 1 + packages/pg-pool/test/events.js | 20 +++++++++++++++++++ packages/pg/lib/client.js | 1 + packages/pg/lib/native/client.js | 1 + packages/pg/test/native/evented-api-tests.js | 20 +++++++++++++++++++ .../test/unit/client/configuration-tests.js | 20 +++++++++++++++++++ 8 files changed, 71 insertions(+), 5 deletions(-) diff --git a/docs/pages/apis/pool.mdx b/docs/pages/apis/pool.mdx index 3627dd1c5..453c70fdf 100644 --- a/docs/pages/apis/pool.mdx +++ b/docs/pages/apis/pool.mdx @@ -240,7 +240,8 @@ The number of queued requests waiting on a client when all clients are checked o Whenever the pool establishes a new client connection to the PostgreSQL backend it will emit the `connect` event with the newly connected client. - The event listener does not wait for promises or async functions. If you want to run setup commands on each new client, use the `onConnect` option. (See documentation above.) + The event listener does not wait for promises or async functions. If you want to run setup commands on each new + client, use the `onConnect` option. (See documentation above.) ### acquire @@ -260,9 +261,8 @@ If the backend goes down or a network partition is encountered all the idle, con The error listener is passed the error as the first argument and the client upon which the error occurred as the 2nd argument. The client will be automatically terminated and removed from the pool, it is only passed to the error handler in case you want to inspect it. -
You probably want to add an event listener to the pool to catch background errors!
- Just like other event emitters, if a pool emits an error event and no listeners are added node will emit an - uncaught error and potentially crash your node process. + The pool has a default no-op error listener so an idle client error does not become an uncaught exception. Add your + own listener to observe, log, or otherwise handle these background errors.
### release diff --git a/packages/pg-pool/README.md b/packages/pg-pool/README.md index 80c644788..7d9538d0c 100644 --- a/packages/pg-pool/README.md +++ b/packages/pg-pool/README.md @@ -234,11 +234,14 @@ const pool = new Pool() // attach an error handler to the pool for when a connected, idle client // receives an error by being disconnected, etc pool.on('error', function (error, client) { - // handle this in the same way you would treat process.on('uncaughtException') + // observe or otherwise handle the background error // it is supplied the error as well as the idle client which received the error }) ``` +Pools include a default no-op error listener so an idle client error does not become an uncaught exception. Add your own +listener to observe, log, or otherwise handle these background errors. + #### connect Fired whenever the pool creates a **new** `pg.Client` instance and successfully connects it to the backend. diff --git a/packages/pg-pool/index.js b/packages/pg-pool/index.js index ab514fa88..7977c3a33 100644 --- a/packages/pg-pool/index.js +++ b/packages/pg-pool/index.js @@ -66,6 +66,7 @@ function makeIdleListener(pool, client) { class Pool extends EventEmitter { constructor(options, Client) { super() + this.on('error', () => {}) this.options = Object.assign({}, options) if (options != null && 'password' in options) { diff --git a/packages/pg-pool/test/events.js b/packages/pg-pool/test/events.js index 809c2159a..73c53b619 100644 --- a/packages/pg-pool/test/events.js +++ b/packages/pg-pool/test/events.js @@ -7,6 +7,26 @@ const it = require('mocha').it const Pool = require('../') describe('events', function () { + it('does not throw an unhandled error event', function () { + const pool = new Pool({ Client: mockClient() }) + const expectedError = new Error('unexpected idle client error') + + expect(() => pool.emit('error', expectedError)).to.not.throwException() + }) + + it('emits errors to user listeners', function () { + const pool = new Pool({ Client: mockClient() }) + const expectedError = new Error('unexpected idle client error') + let emittedError + + pool.on('error', function (error) { + emittedError = error + }) + pool.emit('error', expectedError) + + expect(emittedError).to.be(expectedError) + }) + it('emits connect before callback', function (done) { const pool = new Pool() let emittedClient = false diff --git a/packages/pg/lib/client.js b/packages/pg/lib/client.js index 2b13c1de7..df4207fc8 100644 --- a/packages/pg/lib/client.js +++ b/packages/pg/lib/client.js @@ -50,6 +50,7 @@ function coerceNumberOrDefault(value, defaultValue) { class Client extends EventEmitter { constructor(config) { super() + this.on('error', () => {}) this.connectionParameters = new ConnectionParameters(config) this.user = this.connectionParameters.user diff --git a/packages/pg/lib/native/client.js b/packages/pg/lib/native/client.js index 9ec3c8c03..3775edf42 100644 --- a/packages/pg/lib/native/client.js +++ b/packages/pg/lib/native/client.js @@ -22,6 +22,7 @@ const queryQueueLengthDeprecationNotice = nodeUtils.deprecate( const Client = (module.exports = function (config) { EventEmitter.call(this) + this.on('error', () => {}) config = config || {} this._Promise = config.Promise || global.Promise diff --git a/packages/pg/test/native/evented-api-tests.js b/packages/pg/test/native/evented-api-tests.js index 220fcaece..2e150e440 100644 --- a/packages/pg/test/native/evented-api-tests.js +++ b/packages/pg/test/native/evented-api-tests.js @@ -6,6 +6,26 @@ const assert = require('assert') const suite = new helper.Suite() const test = suite.test.bind(suite) +test('does not throw an unhandled error event', function () { + const client = new Client(helper.config) + const expectedError = new Error('unexpected idle client error') + + assert.doesNotThrow(() => client.emit('error', expectedError)) +}) + +test('emits errors to user listeners', function () { + const client = new Client(helper.config) + const expectedError = new Error('unexpected idle client error') + let emittedError + + client.on('error', (error) => { + emittedError = error + }) + client.emit('error', expectedError) + + assert.strictEqual(emittedError, expectedError) +}) + const setupClient = function () { const client = new Client(helper.config) client.connect() diff --git a/packages/pg/test/unit/client/configuration-tests.js b/packages/pg/test/unit/client/configuration-tests.js index 63d4ea649..d6549bc56 100644 --- a/packages/pg/test/unit/client/configuration-tests.js +++ b/packages/pg/test/unit/client/configuration-tests.js @@ -10,6 +10,26 @@ const pgdatabase = process.env['PGDATABASE'] || process.env.USER const pgport = process.env['PGPORT'] || 5432 test('client settings', function () { + test('does not throw an unhandled error event', function () { + const client = new Client() + const expectedError = new Error('unexpected idle client error') + + assert.doesNotThrow(() => client.emit('error', expectedError)) + }) + + test('emits errors to user listeners', function () { + const client = new Client() + const expectedError = new Error('unexpected idle client error') + let emittedError + + client.on('error', (error) => { + emittedError = error + }) + client.emit('error', expectedError) + + assert.strictEqual(emittedError, expectedError) + }) + test('defaults', function () { const client = new Client() assert.equal(client.user, pguser)