diff --git a/src/connection.js b/src/connection.js index 10ab1bb3..45626ac2 100644 --- a/src/connection.js +++ b/src/connection.js @@ -252,6 +252,16 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose } function nextWrite(fn) { + if (!socket) { + // closed() nulls the socket and reconnect() only recreates it on a later + // timer. write() is also reached from the 'data' handler, so a throw here + // has no query to reject and escapes as an uncaughtException. Settle the + // pending queries rather than dropping the write, or the caller hangs. + nextWriteTimer !== null && clearImmediate(nextWriteTimer) + chunk = nextWriteTimer = null + error(Errors.connection('CONNECTION_CLOSED', options, socket)) + return false + } const x = socket.write(chunk, fn) nextWriteTimer !== null && clearImmediate(nextWriteTimer) chunk = nextWriteTimer = null diff --git a/tests/index.js b/tests/index.js index 845c6ce0..2851c782 100644 --- a/tests/index.js +++ b/tests/index.js @@ -2707,6 +2707,32 @@ t('Ensure reserve on query throws proper error', async() => { ] }) +t('Writing to a closed reserved connection rejects instead of crashing', async() => { + let downstream + const proxy = net.createServer(x => { + downstream = x + const upstream = net.connect(5432, '127.0.0.1') + x.pipe(upstream).pipe(x) + x.on('error', () => upstream.destroy()) + upstream.on('error', () => x.destroy()) + }) + + await new Promise(r => proxy.listen(0, r)) + + const sql = postgres({ ...options, host: '127.0.0.1', port: proxy.address().port, max: 1 }) + , reserved = await sql.reserve() + + await reserved`select 1` + downstream.end() + await delay(50) + + const code = await reserved`select 1`.catch(e => e.code) + reserved.release() + proxy.close() + + return ['CONNECTION_CLOSED', code] +}) + t('query during copy error', async() => { const sql = postgres(options) // eslint-disable-line await sql`create table test (id serial primary key, name text)`