-
-
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
Added the missing connect_timeout and keepalives_idle config parameters #1847
Merged
brianc
merged 2 commits into
brianc:master
from
boromisp:connect-timeout-keepalive-idle
May 10, 2019
Merged
Changes from all commits
Commits
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,85 @@ | ||
'use strict' | ||
const net = require('net') | ||
const buffers = require('../../test-buffers') | ||
const helper = require('./test-helper') | ||
|
||
const suite = new helper.Suite() | ||
|
||
const options = { | ||
host: 'localhost', | ||
port: 54321, | ||
connectionTimeoutMillis: 2000, | ||
user: 'not', | ||
database: 'existing' | ||
} | ||
|
||
const serverWithConnectionTimeout = (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')) | ||
// consider all authentication requests as good | ||
} else if (!data[0]) { | ||
socket.write(buffers.authenticationOk()) | ||
// send ReadyForQuery `timeout` ms after authentication | ||
setTimeout(() => socket.write(buffers.readyForQuery()), timeout).unref() | ||
// respond with our canned response | ||
} else { | ||
socket.write(buffers.readyForQuery()) | ||
} | ||
}) | ||
}) | ||
|
||
let closing = false | ||
const closeServer = done => { | ||
if (closing) return | ||
closing = true | ||
|
||
server.close(done) | ||
for (const socket of sockets) { | ||
socket.destroy() | ||
} | ||
} | ||
|
||
server.listen(options.port, options.host, () => callback(closeServer)) | ||
} | ||
|
||
suite.test('successful connection', done => { | ||
serverWithConnectionTimeout(0, closeServer => { | ||
const timeoutId = setTimeout(() => { | ||
throw new Error('Client should have connected successfully but it did not.') | ||
}, 3000) | ||
|
||
const client = new helper.Client(options) | ||
client.connect() | ||
.then(() => client.end()) | ||
.then(() => closeServer(done)) | ||
.catch(err => closeServer(() => done(err))) | ||
.then(() => clearTimeout(timeoutId)) | ||
}) | ||
}) | ||
|
||
suite.test('expired connection timeout', done => { | ||
serverWithConnectionTimeout(options.connectionTimeoutMillis * 2, closeServer => { | ||
const timeoutId = setTimeout(() => { | ||
throw new Error('Client should have emitted an error but it did not.') | ||
}, 3000) | ||
|
||
const client = new helper.Client(options) | ||
client.connect() | ||
.then(() => client.end()) | ||
.then(() => closeServer(() => done(new Error('Connection timeout should have expired but it did not.')))) | ||
.catch(err => { | ||
assert(err instanceof Error) | ||
assert(/timeout expired\s*/.test(err.message)) | ||
closeServer(done) | ||
}) | ||
.then(() => clearTimeout(timeoutId)) | ||
}) | ||
}) |
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,32 @@ | ||
'use strict' | ||
const net = require('net') | ||
const pg = require('../../../lib/index.js') | ||
const helper = require('./test-helper') | ||
|
||
const suite = new helper.Suite() | ||
|
||
suite.test('setting keep alive', done => { | ||
const server = net.createServer(c => { | ||
c.destroy() | ||
server.close() | ||
}) | ||
|
||
server.listen(7777, () => { | ||
const stream = new net.Socket() | ||
stream.setKeepAlive = (enable, initialDelay) => { | ||
assert(enable === true) | ||
assert(initialDelay === 10000) | ||
done() | ||
} | ||
|
||
const client = new pg.Client({ | ||
host: 'localhost', | ||
port: 7777, | ||
keepAlive: true, | ||
keepAliveInitialDelayMillis: 10000, | ||
stream | ||
}) | ||
|
||
client.connect().catch(() => {}) | ||
}) | ||
}) |
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.
I think that
con.once('end', () => {
also should haveclearTimeout(connectionTimeoutHandle)
, use case:connectionTimeoutMillis
set to some value.Application receive SIGTERM or any other shutdown signal, because client still not connected we can not call
client.end()
, so application can callclient.connection.stream.destroy()
.If
destroy
will be called with error, thenerror
event will be raised,connectingErrorHandler
will be executed and timeoutconnectionTimeoutHandle
will be cleared. Everything OK.If
destroy
will be called without arguments,clearTimeout(connectionTimeoutHandle)
will never happened and applucation will be hang up while timeout will not be called.Why we can not call
client.end()
:Current end code:
Because
activeQuery
is undefined while client is not connected,connection.end()
will be called, which itself implies opened connection, because write data to socket.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.
You are correct; the timer should be cleared in that event handler too.
To make sure I understand your use case, are you calling
client.connection.stream.destroy()
in your signal handler if the connection hasn't been established yet, or is there another way to trigger this behavior?And passing in an error, e.g.
client.connection.stream.destroy(new Error('SIGTERM'))
is the workaround?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.
Currently I do not call
stream.destroy()
, but I'd like modify application, because hang up is not cool.stream.destroy(new Error(...))
is workaround, yes. But from other side, destroy called fromclient.end
called without arguments... (which will not work actually in current case, just as example).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 did some testing just to confirm.
Calling
client.end()
before the connection is established seems to wait for the connection to succeed (in which case the timeout will be cleared). There will be noactiveQuery
before connecting successfully.Calling
client.connection.stream.destroy()
(without an error) will reject theclient.connect()
promise with aConnection terminated unexpectedly
error and wait for the timeout.Calling
client.connection.stream.destroy(new Error())
(with an error) will reject theclient.connect()
promise with the passed in error and not wait for the timeout.The only way to make the application hang on this timer is to call
client.connection.stream.destroy()
from your own code. And the solution is to pass in an error.