diff --git a/packages/pg/lib/connection.js b/packages/pg/lib/connection.js index 62d38fa69..4d4767abb 100644 --- a/packages/pg/lib/connection.js +++ b/packages/pg/lib/connection.js @@ -195,7 +195,8 @@ class Connection extends EventEmitter { } sync() { - this._ending = true + // Sync is the extended-query protocol barrier, not a disconnect. + // Only end()/Terminate (and connect-timeout teardown) should set _ending. this._send(syncBuffer) } diff --git a/packages/pg/test/integration/gh-issues/3772-probe-tests.js b/packages/pg/test/integration/gh-issues/3772-probe-tests.js new file mode 100644 index 000000000..900e9624f --- /dev/null +++ b/packages/pg/test/integration/gh-issues/3772-probe-tests.js @@ -0,0 +1,62 @@ +'use strict' + +const helper = require('./../test-helper') +const assert = require('assert') + +const suite = new helper.Suite() + +suite.test('probe: real PostgreSQL backend termination after Sync', function (done) { + const client = new helper.Client({ pipeline: true }) + const killer = new helper.Client() + const events = [] + let finished = false + let timer + + const finish = function (err) { + if (finished) return + finished = true + clearTimeout(timer) + killer.end(() => client.end(() => done(err))) + } + + client.on('error', (err) => { + events.push(`client.error:${err.code || err.message}`) + console.log('3772 probe events:', events) + if (!finished) finish() + }) + + client.on('end', () => { + events.push('client.end') + console.log('3772 probe events:', events) + if (!finished) finish() + }) + + client.connect((err) => { + if (err) return done(err) + + client.query('SELECT pg_backend_pid() AS pid', (err, result) => { + if (err) return done(err) + const pid = result.rows[0].pid + + // Ensure the parameterized query uses the extended protocol and sends Sync. + client.query({ + text: 'SELECT $1::int AS value', + values: [1], + }) + + killer.connect((err) => { + if (err) return done(err) + + killer.query('SELECT pg_terminate_backend($1)', [pid], (err, result) => { + if (err) return done(err) + assert.equal(result.rows[0].pg_terminate_backend, true) + }) + }) + }) + }) + + timer = setTimeout(() => { + console.log('3772 probe timeout events:', events) + finish(new Error(`timed out waiting for connection termination; events=${events.join(',')}`)) + }, 2000) +}) diff --git a/packages/pg/test/integration/gh-issues/3772-tests.js b/packages/pg/test/integration/gh-issues/3772-tests.js new file mode 100644 index 000000000..b7d6abfaf --- /dev/null +++ b/packages/pg/test/integration/gh-issues/3772-tests.js @@ -0,0 +1,56 @@ +'use strict' + +const helper = require('./../test-helper') +const assert = require('assert') + +const suite = new helper.Suite() + +suite.test('real PostgreSQL backend termination after Sync surfaces connection failure', function (done) { + const client = new helper.Client({ pipeline: true }) + const killer = new helper.Client() + let finished = false + let timer + + const finish = function (err) { + if (finished) return + finished = true + clearTimeout(timer) + killer.end(() => client.end(() => done(err))) + } + + client.on('error', function (err) { + assert(err instanceof Error) + finish() + }) + + client.on('end', function () { + if (!finished) finish() + }) + + client.connect(function (err) { + if (err) return done(err) + + client.query('SELECT pg_backend_pid() AS pid', function (err, result) { + if (err) return done(err) + const pid = result.rows[0].pid + + // Force the extended-query protocol, which sends Sync after the query. + client.query({ + text: 'SELECT $1::int AS value', + values: [1], + }) + + killer.connect(function (err) { + if (err) return done(err) + killer.query('SELECT pg_terminate_backend($1)', [pid], function (err, result) { + if (err) return done(err) + assert.equal(result.rows[0].pg_terminate_backend, true) + }) + }) + }) + }) + + timer = setTimeout(function () { + finish(new Error('timed out waiting for PostgreSQL connection termination')) + }, 2000) +}) diff --git a/packages/pg/test/unit/connection/error-tests.js b/packages/pg/test/unit/connection/error-tests.js index 04f1c3f4b..71ca36566 100644 --- a/packages/pg/test/unit/connection/error-tests.js +++ b/packages/pg/test/unit/connection/error-tests.js @@ -29,6 +29,28 @@ suite.test('connection emits ECONNRESET errors during normal operation', functio con.stream.emit('error', e) }) +suite.test('connection emits ECONNRESET errors after Sync (Sync is not disconnect)', function (done) { + const con = new Connection({ stream: new MemoryStream() }) + con.connect() + // Extended-query Sync used to incorrectly set _ending and swallow resets (#3769) + con.sync() + assert.equal(con._ending, false) + assert.emits(con, 'error', function (err) { + assert.equal(err.code, 'ECONNRESET') + done() + }) + const e = new Error('Connection Reset') + e.code = 'ECONNRESET' + con.stream.emit('error', e) +}) + +suite.test('connection does not set _ending when calling sync()', function () { + const con = new Connection({ stream: new MemoryStream() }) + con.connect() + con.sync() + assert.equal(con._ending, false) +}) + suite.test('connection does not emit ECONNRESET errors during disconnect', function (done) { const con = new Connection({ stream: new MemoryStream() }) con.connect()