From 427c8ef5930c73ae7a3ac7731d8463ae044172bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adem=C3=ADlson=20Tonato?= Date: Mon, 5 Jan 2026 17:24:45 +0000 Subject: [PATCH] fix: end() hangs forever after ECONNRESET error - Clear query variable in errored() after rejecting query - Clear query variable in closed() when hadError is true - Add test to verify end() completes after connection errors Fixes #1130 --- src/connection.js | 11 +++++++++-- tests/index.js | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/connection.js b/src/connection.js index f110cfc1..01e163ca 100644 --- a/src/connection.js +++ b/src/connection.js @@ -388,7 +388,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose function errored(err) { stream && (stream.destroy(err), stream = null) - query && queryError(query, err) + query && (queryError(query, err), query = null) initial && (queryError(initial, err), initial = null) } @@ -449,7 +449,14 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose if (initial) return reconnect() - !hadError && (query || sent.length) && error(Errors.connection('CONNECTION_CLOSED', options, socket)) + if (hadError) { + // Clear query when connection closes with error (e.g., ECONNRESET) + query && (queryError(query, Errors.connection('CONNECTION_CLOSED', options, socket)), query = null) + while (sent.length) + queryError(sent.shift(), Errors.connection('CONNECTION_CLOSED', options, socket)) + } else { + (query || sent.length) && error(Errors.connection('CONNECTION_CLOSED', options, socket)) + } closedTime = performance.now() hadError && options.shared.retries++ delay = (typeof backoff === 'function' ? backoff(options.shared.retries) : backoff) * 1000 diff --git a/tests/index.js b/tests/index.js index 23f86dcd..64f979e1 100644 --- a/tests/index.js +++ b/tests/index.js @@ -2637,4 +2637,43 @@ t('query during copy error', async() => { 'COPY_IN_PROGRESS', error.code, await sql`drop table test` ] +}) + +t('end() completes after ECONNRESET error', { timeout: 5 }, async() => { + // Create a real connection, then terminate the backend to simulate ECONNRESET + const sql = postgres({ ...options, max: 1 }) + const sql2 = postgres({ ...options, max: 1 }) + + // Get the connection's backend PID + const [{ pid }] = await sql`select pg_backend_pid() as pid` + + // Start a query and immediately terminate the backend connection + const queryPromise = sql.unsafe('SELECT pg_sleep(1)').catch(e => e) + + // Terminate the backend right after query starts using a different connection + await delay(10) + await sql2`select pg_terminate_backend(${ pid })` + + // Wait for the query to fail with ECONNRESET + const error = await queryPromise + + // Verify we got a connection error (57P01 = terminating connection, ECONNRESET, or CONNECTION_CLOSED) + if (!error || (error.code !== 'ECONNRESET' && error.code !== 'CONNECTION_CLOSED' && error.code !== '57P01' && !error.message.includes('terminated'))) { + await sql.end() + await sql2.end() + throw new Error('Expected connection error, got: ' + (error ? (error.code || error.message) : 'no error')) + } + + // Now try to end the connection - this should complete, not hang + const endStart = Date.now() + await sql.end() + const endDuration = Date.now() - endStart + + await sql2.end() + + // end() should complete quickly (within 1 second), not hang forever + return [ + true, + endDuration < 1000 + ] }) \ No newline at end of file