-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handle bad message ordering - make it catchable. Fixes 3174 #3289
Merged
Merged
Changes from 5 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
a221d7c
Handle bad message ordering - make it catchable. Fixes 3174
brianc e57c95f
Close client in test
brianc 1b5741e
Mess w/ github action settings
brianc a9248f6
update ci config
brianc 4ed2bcc
Remove redundant tests
brianc 54dc7af
Update code to use handle error event
brianc 2d67ef6
Add tests for commandComplete message being out of order
brianc 50306bc
Lint fix
brianc e10bf64
Fix native tests
brianc d912cbd
Fix lint again...airport computer not my friend
brianc 93b9fad
Not a native issue
brianc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
const net = require('net') | ||
const buffers = require('../../test-buffers') | ||
const helper = require('../test-helper') | ||
const assert = require('assert') | ||
const cli = require('../../cli') | ||
|
||
const suite = new helper.Suite() | ||
|
||
const options = { | ||
host: 'localhost', | ||
port: Math.floor(Math.random() * 2000) + 2000, | ||
connectionTimeoutMillis: 2000, | ||
user: 'not', | ||
database: 'existing', | ||
} | ||
|
||
const startMockServer = (port, timeout, callback) => { | ||
const sockets = new Set() | ||
|
||
const server = net.createServer((socket) => { | ||
sockets.add(socket) | ||
socket.once('end', () => sockets.delete(socket)) | ||
|
||
socket.on('data', (data) => { | ||
// deny request for SSL | ||
if (data.length === 8) { | ||
socket.write(Buffer.from('N', 'utf8')) | ||
return | ||
// consider all authentication requests as good | ||
} | ||
// the initial message coming in has a 0 message type for authentication negotiation | ||
if (!data[0]) { | ||
socket.write(buffers.authenticationOk()) | ||
// send ReadyForQuery `timeout` ms after authentication | ||
socket.write(buffers.readyForQuery()) | ||
return | ||
// respond with our canned response | ||
} | ||
const code = data.toString('utf8', 0, 1) | ||
switch (code) { | ||
// parse | ||
case 'P': | ||
socket.write(buffers.parseComplete()) | ||
socket.write(buffers.bindComplete()) | ||
socket.write(buffers.rowDescription()) | ||
socket.write(buffers.dataRow()) | ||
socket.write(buffers.commandComplete('FOO BAR')) | ||
socket.write(buffers.readyForQuery()) | ||
// this message is invalid, but sometimes sent out of order when using proxies or pg-bouncer | ||
setImmediate(() => { | ||
socket.write(buffers.parseComplete()) | ||
}) | ||
break | ||
case 'Q': | ||
socket.write(buffers.rowDescription()) | ||
socket.write(buffers.dataRow()) | ||
socket.write(buffers.commandComplete('FOO BAR')) | ||
socket.write(buffers.readyForQuery()) | ||
// this message is invalid, but sometimes sent out of order when using proxies or pg-bouncer | ||
setImmediate(() => { | ||
socket.write(buffers.parseComplete()) | ||
}) | ||
default: | ||
// console.log('got code', code) | ||
} | ||
}) | ||
}) | ||
|
||
const closeServer = () => { | ||
for (const socket of sockets) { | ||
socket.destroy() | ||
} | ||
return new Promise((resolve) => { | ||
server.close(resolve) | ||
}) | ||
} | ||
|
||
server.listen(port, options.host, () => callback(closeServer)) | ||
} | ||
|
||
const delay = (ms) => | ||
new Promise((resolve) => { | ||
setTimeout(resolve, ms) | ||
}) | ||
|
||
suite.testAsync('Out of order parseComplete on simple query is catchable', async () => { | ||
const closeServer = await new Promise((resolve, reject) => { | ||
return startMockServer(options.port, 0, (closeServer) => resolve(closeServer)) | ||
}) | ||
const client = new helper.Client(options) | ||
await client.connect() | ||
|
||
let errorHit = false | ||
client.on('error', () => { | ||
errorHit = true | ||
}) | ||
|
||
await client.query('SELECT NOW') | ||
await delay(50) | ||
await client.query('SELECT NOW') | ||
await delay(50) | ||
await client.query('SELECT NOW') | ||
await delay(50) | ||
await client.end() | ||
assert(cli.native || errorHit) | ||
|
||
await closeServer() | ||
}) | ||
|
||
suite.testAsync('Out of order parseComplete on extended query is catchable', async () => { | ||
const closeServer = await new Promise((resolve, reject) => { | ||
return startMockServer(options.port, 0, (closeServer) => resolve(closeServer)) | ||
}) | ||
const client = new helper.Client(options) | ||
await client.connect() | ||
|
||
let errorHit = false | ||
client.on('error', () => { | ||
errorHit = true | ||
}) | ||
|
||
await client.query('SELECT $1', ['foo']) | ||
await delay(40) | ||
assert(cli.native || errorHit) | ||
await client.end() | ||
|
||
await closeServer() | ||
}) | ||
|
||
suite.testAsync('Out of order parseComplete on pool is catchable', async () => { | ||
const closeServer = await new Promise((resolve, reject) => { | ||
return startMockServer(options.port, 0, (closeServer) => resolve(closeServer)) | ||
}) | ||
const pool = new helper.pg.Pool(options) | ||
|
||
let errorHit = false | ||
pool.on('error', () => { | ||
errorHit = true | ||
}) | ||
|
||
await pool.query('SELECT $1', ['foo']) | ||
await delay(100) | ||
assert(cli.native || errorHit) | ||
|
||
await pool.end() | ||
await closeServer() | ||
}) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this use
_handleErrorEvent
, since the connection is in an unpredictable state afterwards?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see how using
_handleErrorEvent
might be a good idea here too, I think what @brianc communicated to me about this PR is that he thinks the issue is always related to an unexpected message arriving at a client which is in the idle state and so if that is the case it might not be required to do the full_handleErrorEvent
but I can see how maybe doing full_handleErrorEvent
is more robust.