Skip to content

Commit 0bc0d9a

Browse files
committed
Fix double-sync crash on postgres 9.x
1 parent 7ffe68e commit 0bc0d9a

File tree

3 files changed

+36
-3
lines changed

3 files changed

+36
-3
lines changed

packages/pg/lib/query.js

+12-3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class Query extends EventEmitter {
3131
this.isPreparedStatement = false
3232
this._canceledDueToError = false
3333
this._promise = null
34+
this._hasSentSync = false
3435
}
3536

3637
requiresPreparation() {
@@ -100,7 +101,8 @@ class Query extends EventEmitter {
100101
this._checkForMultirow()
101102
this._result.addCommandComplete(msg)
102103
// need to sync after each command complete of a prepared statement
103-
if (this.isPreparedStatement) {
104+
if (this.isPreparedStatement && !this._hasSentSync) {
105+
this._hasSentSync = true
104106
con.sync()
105107
}
106108
}
@@ -109,7 +111,8 @@ class Query extends EventEmitter {
109111
// the backend will send an emptyQuery message but *not* a command complete message
110112
// execution on the connection will hang until the backend receives a sync message
111113
handleEmptyQuery(con) {
112-
if (this.isPreparedStatement) {
114+
if (this.isPreparedStatement && !this._hasSentSync) {
115+
this._hasSentSync = true
113116
con.sync()
114117
}
115118
}
@@ -126,7 +129,13 @@ class Query extends EventEmitter {
126129

127130
handleError(err, connection) {
128131
// need to sync after error during a prepared statement
129-
if (this.isPreparedStatement) {
132+
// in postgres 9.6 the backend sends both a command complete and error response
133+
// to a query which has timed out on rare, random occasions. If we send sync twice we will receive
134+
// to 'readyForQuery' events. I think this might be a bug in postgres 9.6, but I'm not sure...
135+
// the docs here: https://www.postgresql.org/docs/9.6/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY
136+
// say "Therefore, an Execute phase is always terminated by the appearance of exactly one of these messages: CommandComplete, EmptyQueryResponse (if the portal was created from an empty query string), ErrorResponse, or PortalSuspended."
137+
if (this.isPreparedStatement && !this._hasSentSync) {
138+
this._hasSentSync = true
130139
connection.sync()
131140
}
132141
if (this._canceledDueToError) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const pg = require('../../../lib')
2+
const helper = require('../test-helper')
3+
const suite = new helper.Suite()
4+
5+
suite.testAsync('timeout causing query crashes', async () => {
6+
const client = new helper.Client()
7+
await client.connect()
8+
await client.query('CREATE TEMP TABLE foobar( name TEXT NOT NULL, id SERIAL)')
9+
client.query('BEGIN')
10+
await client.query("SET LOCAL statement_timeout TO '1ms'")
11+
let count = 0
12+
while (count++ < 5000) {
13+
try {
14+
await client.query('INSERT INTO foobar(name) VALUES ($1)', [Math.random() * 1000 + ''])
15+
} catch (e) {
16+
await client.query('ROLLBACK')
17+
}
18+
}
19+
await client.end()
20+
})

packages/pg/test/integration/gh-issues/2085-tests.js

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ var assert = require('assert')
44

55
const suite = new helper.Suite()
66

7+
if (process.env.PGTESTNOSSL) {
8+
return
9+
}
10+
711
suite.testAsync('it should connect over ssl', async () => {
812
const ssl = helper.args.native
913
? 'require'

0 commit comments

Comments
 (0)