From 7edfdf721f48445a82ce9dc572d5fb2ead48fda9 Mon Sep 17 00:00:00 2001 From: Charmander <~@charmander.me> Date: Tue, 14 Nov 2017 22:21:50 -0800 Subject: [PATCH 1/9] Add tests for query callbacks after connection-level errors --- .../connection-pool/error-tests.js | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/test/integration/connection-pool/error-tests.js b/test/integration/connection-pool/error-tests.js index fbc0fdedf..253f85a7a 100644 --- a/test/integration/connection-pool/error-tests.js +++ b/test/integration/connection-pool/error-tests.js @@ -5,8 +5,6 @@ const pg = helper.pg // first make pool hold 2 clients pg.defaults.poolSize = 2 -const pool = new pg.Pool() - const suite = new helper.Suite() suite.test('connecting to invalid port', (cb) => { const pool = new pg.Pool({ port: 13801 }) @@ -14,6 +12,7 @@ suite.test('connecting to invalid port', (cb) => { }) suite.test('errors emitted on pool', (cb) => { + const pool = new pg.Pool() // get first client pool.connect(assert.success(function (client, done) { client.id = 1 @@ -48,3 +47,45 @@ suite.test('errors emitted on pool', (cb) => { }) })) }) + +suite.test('connection-level errors cause queued queries to fail', (cb) => { + const pool = new pg.Pool() + pool.connect(assert.success((client, done) => { + client.query('SELECT pg_terminate_backend(pg_backend_pid())', assert.calls((err) => { + assert.equal(err.code, '57P01') + })) + + pool.once('error', assert.calls((err, brokenClient) => { + assert.equal(client, brokenClient) + })) + + client.query('SELECT 1', assert.calls((err) => { + assert.equal(err.code, 'EPIPE') + + done() + pool.end() + cb() + })) + })) +}) + +suite.test('connection-level errors cause future queries to fail', (cb) => { + const pool = new pg.Pool() + pool.connect(assert.success((client, done) => { + client.query('SELECT pg_terminate_backend(pg_backend_pid())', assert.calls((err) => { + assert.equal(err.code, '57P01') + })) + + pool.once('error', assert.calls((err, brokenClient) => { + assert.equal(client, brokenClient) + + client.query('SELECT 1', assert.calls((err) => { + assert.equal(err.message, 'Client has encountered a connection error and is not queryable') + + done() + pool.end() + cb() + })) + })) + })) +}) From 6cba93daa20c73af180897dc934feb32f8df3961 Mon Sep 17 00:00:00 2001 From: Charmander <~@charmander.me> Date: Tue, 14 Nov 2017 22:24:29 -0800 Subject: [PATCH 2/9] Ensure callbacks are executed for all queued queries after connection-level errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Separates socket errors from error messages, sends socket errors to all queries in the queue, marks clients as unusable after socket errors. This is not very pleasant but should maintain backwards compatibility…? --- lib/client.js | 60 ++++++++++++++++--- lib/connection.js | 3 +- lib/query.js | 11 +--- .../client/error-handling-tests.js | 12 ++++ 4 files changed, 69 insertions(+), 17 deletions(-) diff --git a/lib/client.js b/lib/client.js index 3228571d5..f5df1721c 100644 --- a/lib/client.js +++ b/lib/client.js @@ -36,6 +36,7 @@ var Client = function (config) { this._connecting = false this._connected = false this._connectionError = false + this._queryable = true this.connection = c.connection || new Connection({ stream: c.stream, @@ -126,15 +127,39 @@ Client.prototype.connect = function (callback) { } const connectedErrorHandler = (err) => { + this._queryable = false + + const enqueueError = (query) => { + process.nextTick(() => { + query.handleError(err, con) + }) + } + if (this.activeQuery) { - var activeQuery = self.activeQuery + enqueueError(this.activeQuery) this.activeQuery = null - return activeQuery.handleError(err, con) } + + this.queryQueue.forEach(enqueueError) + this.queryQueue = [] + this.emit('error', err) } + const connectedErrorMessageHandler = (msg) => { + const activeQuery = this.activeQuery + + if (!activeQuery) { + connectedErrorHandler(msg) + return + } + + this.activeQuery = null + activeQuery.handleError(msg, con) + } + con.on('error', connectingErrorHandler) + con.on('errorMessage', connectingErrorHandler) // hook up query handling events to connection // after the connection initially becomes ready for queries @@ -143,7 +168,9 @@ Client.prototype.connect = function (callback) { self._connected = true self._attachListeners(con) con.removeListener('error', connectingErrorHandler) + con.removeListener('errorMessage', connectingErrorHandler) con.on('error', connectedErrorHandler) + con.on('errorMessage', connectedErrorMessageHandler) // process possible callback argument to Client#connect if (callback) { @@ -353,7 +380,13 @@ Client.prototype._pulseQueryQueue = function () { if (this.activeQuery) { this.readyForQuery = false this.hasExecuted = true - this.activeQuery.submit(this.connection) + + const queryError = this.activeQuery.submit(this.connection) + if (queryError) { + this.activeQuery.handleError(queryError, this.connection) + this.readyForQuery = true + this._pulseQueryQueue() + } } else if (this.hasExecuted) { this.activeQuery = null this.emit('drain') @@ -389,6 +422,16 @@ Client.prototype.query = function (config, values, callback) { query._result._getTypeParser = this._types.getTypeParser.bind(this._types) } + if (!this._queryable) { + query.handleError(new Error('Client has encountered a connection error and is not queryable'), this.connection) + return + } + + if (this._ending) { + query.handleError(new Error('Client was closed and is not queryable'), this.connection) + return + } + this.queryQueue.push(query) this._pulseQueryQueue() return result @@ -396,18 +439,19 @@ Client.prototype.query = function (config, values, callback) { Client.prototype.end = function (cb) { this._ending = true + if (this.activeQuery) { // if we have an active query we need to force a disconnect // on the socket - otherwise a hung query could block end forever - this.connection.stream.destroy(new Error('Connection terminated by user')) - return cb ? cb() : Promise.resolve() + this.connection.stream.destroy() + } else { + this.connection.end() } + if (cb) { - this.connection.end() this.connection.once('end', cb) } else { - return new global.Promise((resolve, reject) => { - this.connection.end() + return new Promise((resolve) => { this.connection.once('end', resolve) }) } diff --git a/lib/connection.js b/lib/connection.js index 0f98cb062..cdaafe4d7 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -112,10 +112,11 @@ Connection.prototype.attachListeners = function (stream) { var packet = self._reader.read() while (packet) { var msg = self.parseMessage(packet) + var eventName = msg.name === 'error' ? 'errorMessage' : msg.name if (self._emitMessage) { self.emit('message', msg) } - self.emit(msg.name, msg) + self.emit(eventName, msg) packet = self._reader.read() } }) diff --git a/lib/query.js b/lib/query.js index 4d483ac7a..0bb75741f 100644 --- a/lib/query.js +++ b/lib/query.js @@ -147,22 +147,17 @@ Query.prototype.handleError = function (err, connection) { Query.prototype.submit = function (connection) { if (typeof this.text !== 'string' && typeof this.name !== 'string') { - const err = new Error('A query must have either text or a name. Supplying neither is unsupported.') - connection.emit('error', err) - connection.emit('readyForQuery') - return + return new Error('A query must have either text or a name. Supplying neither is unsupported.') } if (this.values && !Array.isArray(this.values)) { - const err = new Error('Query values must be an array') - connection.emit('error', err) - connection.emit('readyForQuery') - return + return new Error('Query values must be an array') } if (this.requiresPreparation()) { this.prepare(connection) } else { connection.query(this.text) } + return null } Query.prototype.hasBeenParsed = function (connection) { diff --git a/test/integration/client/error-handling-tests.js b/test/integration/client/error-handling-tests.js index 55372d141..f8dceb05f 100644 --- a/test/integration/client/error-handling-tests.js +++ b/test/integration/client/error-handling-tests.js @@ -50,6 +50,18 @@ suite.test('re-using connections results in promise rejection', (done) => { }) }) +suite.test('using a client after closing it results in error', (done) => { + const client = new Client() + client.connect(() => { + client.end(assert.calls(() => { + client.query('SELECT 1', assert.calls((err) => { + assert.equal(err.message, 'Client was closed and is not queryable') + done() + })) + })) + }) +}) + suite.test('query receives error on client shutdown', function (done) { var client = new Client() client.connect(assert.success(function () { From 7b6b7a198dc0f45bfaee6d5150127fa74f70da28 Mon Sep 17 00:00:00 2001 From: Charmander <~@charmander.me> Date: Tue, 14 Nov 2017 23:11:30 -0800 Subject: [PATCH 3/9] Always call `handleError` asynchronously MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This doesn’t match the original behaviour of the type errors, but it’s correct. --- lib/client.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/client.js b/lib/client.js index f5df1721c..39376e025 100644 --- a/lib/client.js +++ b/lib/client.js @@ -383,9 +383,11 @@ Client.prototype._pulseQueryQueue = function () { const queryError = this.activeQuery.submit(this.connection) if (queryError) { - this.activeQuery.handleError(queryError, this.connection) - this.readyForQuery = true - this._pulseQueryQueue() + process.nextTick(() => { + this.activeQuery.handleError(queryError, this.connection) + this.readyForQuery = true + this._pulseQueryQueue() + }) } } else if (this.hasExecuted) { this.activeQuery = null @@ -423,12 +425,16 @@ Client.prototype.query = function (config, values, callback) { } if (!this._queryable) { - query.handleError(new Error('Client has encountered a connection error and is not queryable'), this.connection) + process.nextTick(() => { + query.handleError(new Error('Client has encountered a connection error and is not queryable'), this.connection) + }) return } if (this._ending) { - query.handleError(new Error('Client was closed and is not queryable'), this.connection) + process.nextTick(() => { + query.handleError(new Error('Client was closed and is not queryable'), this.connection) + }) return } From fdf5a4ad6cc6521b508147db52cde20cb9fabc2d Mon Sep 17 00:00:00 2001 From: Charmander <~@charmander.me> Date: Wed, 15 Nov 2017 04:32:59 -0800 Subject: [PATCH 4/9] Fix return value of `Client.prototype.query` in immediate error cases --- lib/client.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/client.js b/lib/client.js index 39376e025..411f743cf 100644 --- a/lib/client.js +++ b/lib/client.js @@ -428,14 +428,14 @@ Client.prototype.query = function (config, values, callback) { process.nextTick(() => { query.handleError(new Error('Client has encountered a connection error and is not queryable'), this.connection) }) - return + return result } if (this._ending) { process.nextTick(() => { query.handleError(new Error('Client was closed and is not queryable'), this.connection) }) - return + return result } this.queryQueue.push(query) From 57bd1441c26e3889bb9e4fc72c6e9ae0dcf6cfb3 Mon Sep 17 00:00:00 2001 From: Charmander <~@charmander.me> Date: Wed, 15 Nov 2017 04:51:32 -0800 Subject: [PATCH 5/9] Mark clients with closed connections as unusable consistently --- lib/client.js | 4 ++-- test/integration/connection-pool/error-tests.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/client.js b/lib/client.js index 411f743cf..9653f7dcc 100644 --- a/lib/client.js +++ b/lib/client.js @@ -208,10 +208,10 @@ Client.prototype.connect = function (callback) { if (callback) { callback(error) } else { - this.emit('error', error) + connectedErrorHandler(error) } } else if (!this._connectionError) { - this.emit('error', error) + connectedErrorHandler(error) } } this.emit('end') diff --git a/test/integration/connection-pool/error-tests.js b/test/integration/connection-pool/error-tests.js index 253f85a7a..4d7bb9c1f 100644 --- a/test/integration/connection-pool/error-tests.js +++ b/test/integration/connection-pool/error-tests.js @@ -60,7 +60,7 @@ suite.test('connection-level errors cause queued queries to fail', (cb) => { })) client.query('SELECT 1', assert.calls((err) => { - assert.equal(err.code, 'EPIPE') + assert.equal(err.message, 'Connection terminated unexpectedly') done() pool.end() From a55da8a0b260bbe7d40071ca31bf6c9dff321c1e Mon Sep 17 00:00:00 2001 From: Charmander <~@charmander.me> Date: Fri, 1 Dec 2017 16:30:44 -0800 Subject: [PATCH 6/9] Add tests for error event when connecting Client --- test/integration/client/error-handling-tests.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/integration/client/error-handling-tests.js b/test/integration/client/error-handling-tests.js index f8dceb05f..97b0ce83f 100644 --- a/test/integration/client/error-handling-tests.js +++ b/test/integration/client/error-handling-tests.js @@ -151,6 +151,9 @@ suite.test('when connecting to an invalid host with callback', function (done) { var client = new Client({ user: 'very invalid username' }) + client.on('error', () => { + assert.fail('unexpected error event when connecting') + }) client.connect(function (error, client) { assert(error instanceof Error) done() @@ -161,6 +164,9 @@ suite.test('when connecting to invalid host with promise', function (done) { var client = new Client({ user: 'very invalid username' }) + client.on('error', () => { + assert.fail('unexpected error event when connecting') + }) client.connect().catch((e) => done()) }) From 99ba0605616eaee73c43228f45df6ec47577ab39 Mon Sep 17 00:00:00 2001 From: Charmander <~@charmander.me> Date: Fri, 4 May 2018 16:46:37 -0700 Subject: [PATCH 7/9] Ensure the promise and callback versions of Client#connect always have the same behaviour --- lib/client.js | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/lib/client.js b/lib/client.js index 9653f7dcc..140f505b2 100644 --- a/lib/client.js +++ b/lib/client.js @@ -53,16 +53,15 @@ var Client = function (config) { util.inherits(Client, EventEmitter) -Client.prototype.connect = function (callback) { +Client.prototype._connect = function (callback) { var self = this var con = this.connection if (this._connecting || this._connected) { const err = new Error('Client has already been connected. You cannot reuse a client.') - if (callback) { + process.nextTick(() => { callback(err) - return undefined - } - return Promise.reject(err) + }) + return } this._connecting = true @@ -220,16 +219,23 @@ Client.prototype.connect = function (callback) { con.on('notice', function (msg) { self.emit('notice', msg) }) +} + +Client.prototype.connect = function (callback) { + if (callback) { + this._connect(callback) + return + } - if (!callback) { - return new global.Promise((resolve, reject) => { - this.once('error', reject) - this.once('connect', () => { - this.removeListener('error', reject) + return new Promise((resolve, reject) => { + this._connect((error) => { + if (error) { + reject(error) + } else { resolve() - }) + } }) - } + }) } Client.prototype._attachListeners = function (con) { From 913a7e236bf9639f8dcde87fc8b199a7b344958d Mon Sep 17 00:00:00 2001 From: Charmander <~@charmander.me> Date: Fri, 4 May 2018 20:01:34 -0700 Subject: [PATCH 8/9] Give same error to queued queries as to active query when ending and do so in the native Client as well. --- lib/client.js | 45 ++++--- lib/native/client.js | 121 +++++++++++------- .../client/error-handling-tests.js | 5 +- .../connection-pool/error-tests.js | 18 ++- 4 files changed, 115 insertions(+), 74 deletions(-) diff --git a/lib/client.js b/lib/client.js index 140f505b2..1aadac08f 100644 --- a/lib/client.js +++ b/lib/client.js @@ -53,6 +53,22 @@ var Client = function (config) { util.inherits(Client, EventEmitter) +Client.prototype._errorAllQueries = function (err) { + const enqueueError = (query) => { + process.nextTick(() => { + query.handleError(err, this.connection) + }) + } + + if (this.activeQuery) { + enqueueError(this.activeQuery) + this.activeQuery = null + } + + this.queryQueue.forEach(enqueueError) + this.queryQueue.length = 0 +} + Client.prototype._connect = function (callback) { var self = this var con = this.connection @@ -127,21 +143,7 @@ Client.prototype._connect = function (callback) { const connectedErrorHandler = (err) => { this._queryable = false - - const enqueueError = (query) => { - process.nextTick(() => { - query.handleError(err, con) - }) - } - - if (this.activeQuery) { - enqueueError(this.activeQuery) - this.activeQuery = null - } - - this.queryQueue.forEach(enqueueError) - this.queryQueue = [] - + this._errorAllQueries(err) this.emit('error', err) } @@ -192,17 +194,17 @@ Client.prototype._connect = function (callback) { }) con.once('end', () => { - if (this.activeQuery) { - var disconnectError = new Error('Connection terminated') - this.activeQuery.handleError(disconnectError, con) - this.activeQuery = null - } + const error = this._ending + ? new Error('Connection terminated') + : new Error('Connection terminated unexpectedly') + + this._errorAllQueries(error) + if (!this._ending) { // if the connection is ended without us calling .end() // on this client then we have an unexpected disconnection // treat this as an error unless we've already emitted an error // during connection. - const error = new Error('Connection terminated unexpectedly') if (this._connecting && !this._connectionError) { if (callback) { callback(error) @@ -213,6 +215,7 @@ Client.prototype._connect = function (callback) { connectedErrorHandler(error) } } + this.emit('end') }) diff --git a/lib/native/client.js b/lib/native/client.js index bed548ad8..c9dca63c6 100644 --- a/lib/native/client.js +++ b/lib/native/client.js @@ -32,8 +32,10 @@ var Client = module.exports = function (config) { }) this._queryQueue = [] - this._connected = false + this._ending = false this._connecting = false + this._connected = false + this._queryable = true // keep these on the object for legacy reasons // for the time being. TODO: deprecate all this jazz @@ -52,50 +54,48 @@ Client.Query = NativeQuery util.inherits(Client, EventEmitter) +Client.prototype._errorAllQueries = function (err) { + const enqueueError = (query) => { + process.nextTick(() => { + query.native = this.native + query.handleError(err) + }) + } + + if (this._hasActiveQuery()) { + enqueueError(this._activeQuery) + this._activeQuery = null + } + + this._queryQueue.forEach(enqueueError) + this._queryQueue.length = 0 +} + // connect to the backend // pass an optional callback to be called once connected // or with an error if there was a connection error -// if no callback is passed and there is a connection error -// the client will emit an error event. -Client.prototype.connect = function (cb) { +Client.prototype._connect = function (cb) { var self = this - var onError = function (err) { - if (cb) return cb(err) - return self.emit('error', err) - } - - var result - if (!cb) { - var resolveOut, rejectOut - cb = (err) => err ? rejectOut(err) : resolveOut() - result = new global.Promise(function (resolve, reject) { - resolveOut = resolve - rejectOut = reject - }) - } - if (this._connecting) { process.nextTick(() => cb(new Error('Client has already been connected. You cannot reuse a client.'))) - return result + return } this._connecting = true this.connectionParameters.getLibpqConnectionString(function (err, conString) { - if (err) return onError(err) + if (err) return cb(err) self.native.connect(conString, function (err) { - if (err) return onError(err) + if (err) return cb(err) // set internal states to connected self._connected = true // handle connection errors from the native layer self.native.on('error', function (err) { - // error will be handled by active query - if (self._activeQuery && self._activeQuery.state !== 'end') { - return - } + self._queryable = false + self._errorAllQueries(err) self.emit('error', err) }) @@ -110,12 +110,26 @@ Client.prototype.connect = function (cb) { self.emit('connect') self._pulseQueryQueue(true) - // possibly call the optional callback - if (cb) cb() + cb() }) }) +} - return result +Client.prototype.connect = function (callback) { + if (callback) { + this._connect(callback) + return + } + + return new Promise((resolve, reject) => { + this._connect((error) => { + if (error) { + reject(error) + } else { + resolve() + } + }) + }) } // send a query to the server @@ -129,26 +143,43 @@ Client.prototype.connect = function (cb) { // optional string rowMode = 'array' for an array of results // } Client.prototype.query = function (config, values, callback) { + var query + var result + if (typeof config.submit === 'function') { + result = query = config // accept query(new Query(...), (err, res) => { }) style if (typeof values === 'function') { config.callback = values } - this._queryQueue.push(config) - this._pulseQueryQueue() - return config + } else { + query = new NativeQuery(config, values, callback) + if (!query.callback) { + let resolveOut, rejectOut + result = new Promise((resolve, reject) => { + resolveOut = resolve + rejectOut = reject + }) + query.callback = (err, res) => err ? rejectOut(err) : resolveOut(res) + } } - var query = new NativeQuery(config, values, callback) - var result - if (!query.callback) { - let resolveOut, rejectOut - result = new Promise((resolve, reject) => { - resolveOut = resolve - rejectOut = reject + if (!this._queryable) { + query.native = this.native + process.nextTick(() => { + query.handleError(new Error('Client has encountered a connection error and is not queryable')) + }) + return result + } + + if (this._ending) { + query.native = this.native + process.nextTick(() => { + query.handleError(new Error('Client was closed and is not queryable')) }) - query.callback = (err, res) => err ? rejectOut(err) : resolveOut(res) + return result } + this._queryQueue.push(query) this._pulseQueryQueue() return result @@ -157,6 +188,9 @@ Client.prototype.query = function (config, values, callback) { // disconnect from the backend server Client.prototype.end = function (cb) { var self = this + + this._ending = true + if (!this._connected) { this.once('connect', this.end.bind(this, cb)) } @@ -170,12 +204,7 @@ Client.prototype.end = function (cb) { }) } this.native.end(function () { - // send an error to the active query - if (self._hasActiveQuery()) { - var msg = 'Connection terminated' - self._queryQueue.length = 0 - self._activeQuery.handleError(new Error(msg)) - } + self._errorAllQueries(new Error('Connection terminated')) self.emit('end') if (cb) cb() }) diff --git a/test/integration/client/error-handling-tests.js b/test/integration/client/error-handling-tests.js index 97b0ce83f..3d4679db9 100644 --- a/test/integration/client/error-handling-tests.js +++ b/test/integration/client/error-handling-tests.js @@ -73,12 +73,9 @@ suite.test('query receives error on client shutdown', function (done) { client.query(new pg.Query(config), assert.calls(function (err, res) { assert(err instanceof Error) queryError = err + done() })) setTimeout(() => client.end(), 50) - client.once('end', () => { - assert(queryError instanceof Error) - done() - }) })) }) diff --git a/test/integration/connection-pool/error-tests.js b/test/integration/connection-pool/error-tests.js index 4d7bb9c1f..a7a8cad53 100644 --- a/test/integration/connection-pool/error-tests.js +++ b/test/integration/connection-pool/error-tests.js @@ -52,7 +52,11 @@ suite.test('connection-level errors cause queued queries to fail', (cb) => { const pool = new pg.Pool() pool.connect(assert.success((client, done) => { client.query('SELECT pg_terminate_backend(pg_backend_pid())', assert.calls((err) => { - assert.equal(err.code, '57P01') + if (helper.args.native) { + assert.ok(err) + } else { + assert.equal(err.code, '57P01') + } })) pool.once('error', assert.calls((err, brokenClient) => { @@ -60,7 +64,11 @@ suite.test('connection-level errors cause queued queries to fail', (cb) => { })) client.query('SELECT 1', assert.calls((err) => { - assert.equal(err.message, 'Connection terminated unexpectedly') + if (helper.args.native) { + assert.ok(err) + } else { + assert.equal(err.message, 'Connection terminated unexpectedly') + } done() pool.end() @@ -73,7 +81,11 @@ suite.test('connection-level errors cause future queries to fail', (cb) => { const pool = new pg.Pool() pool.connect(assert.success((client, done) => { client.query('SELECT pg_terminate_backend(pg_backend_pid())', assert.calls((err) => { - assert.equal(err.code, '57P01') + if (helper.args.native) { + assert.ok(err) + } else { + assert.equal(err.code, '57P01') + } })) pool.once('error', assert.calls((err, brokenClient) => { From dd127f237d44e91e56c8e36a08dd5384c64167c0 Mon Sep 17 00:00:00 2001 From: Charmander <~@charmander.me> Date: Fri, 4 May 2018 21:34:54 -0700 Subject: [PATCH 9/9] Restore original ordering between queued query callbacks and 'end' event --- lib/client.js | 4 +++- lib/native/client.js | 7 +++++-- test/integration/client/error-handling-tests.js | 5 ++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/client.js b/lib/client.js index 1aadac08f..a50759a3b 100644 --- a/lib/client.js +++ b/lib/client.js @@ -216,7 +216,9 @@ Client.prototype._connect = function (callback) { } } - this.emit('end') + process.nextTick(() => { + this.emit('end') + }) }) con.on('notice', function (msg) { diff --git a/lib/native/client.js b/lib/native/client.js index c9dca63c6..b18ff6ffa 100644 --- a/lib/native/client.js +++ b/lib/native/client.js @@ -205,8 +205,11 @@ Client.prototype.end = function (cb) { } this.native.end(function () { self._errorAllQueries(new Error('Connection terminated')) - self.emit('end') - if (cb) cb() + + process.nextTick(() => { + self.emit('end') + if (cb) cb() + }) }) return result } diff --git a/test/integration/client/error-handling-tests.js b/test/integration/client/error-handling-tests.js index 3d4679db9..97b0ce83f 100644 --- a/test/integration/client/error-handling-tests.js +++ b/test/integration/client/error-handling-tests.js @@ -73,9 +73,12 @@ suite.test('query receives error on client shutdown', function (done) { client.query(new pg.Query(config), assert.calls(function (err, res) { assert(err instanceof Error) queryError = err - done() })) setTimeout(() => client.end(), 50) + client.once('end', () => { + assert(queryError instanceof Error) + done() + }) })) })