Skip to content

Commit 29211e0

Browse files
committed
Implementation and tests for keepAliveInitialDelayMillis and connectionTimeoutMillis [squashed 4]
1 parent 60c8e87 commit 29211e0

File tree

7 files changed

+158
-18
lines changed

7 files changed

+158
-18
lines changed

Diff for: lib/client.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,15 @@ var Client = function (config) {
4444
stream: c.stream,
4545
ssl: this.connectionParameters.ssl,
4646
keepAlive: c.keepAlive || false,
47-
keepAliveIdleMillis: c.keepAliveIdleMillis || 0,
48-
connectTimeoutMillis: c.connectTimeoutMillis || 0,
47+
keepAliveInitialDelayMillis: c.keepAliveInitialDelayMillis || 0,
4948
encoding: this.connectionParameters.client_encoding || 'utf8'
5049
})
5150
this.queryQueue = []
5251
this.binary = c.binary || defaults.binary
5352
this.processID = null
5453
this.secretKey = null
5554
this.ssl = this.connectionParameters.ssl || false
55+
this._connectionTimeoutMillis = c.connectionTimeoutMillis || 0
5656
}
5757

5858
util.inherits(Client, EventEmitter)
@@ -85,6 +85,14 @@ Client.prototype._connect = function (callback) {
8585
}
8686
this._connecting = true
8787

88+
var connectionTimeoutHandle
89+
if (this._connectionTimeoutMillis > 0) {
90+
connectionTimeoutHandle = setTimeout(() => {
91+
con._ending = true
92+
con.stream.destroy(new Error('timeout expired'))
93+
}, this._connectionTimeoutMillis)
94+
}
95+
8896
if (this.host && this.host.indexOf('/') === 0) {
8997
con.connect(this.host + '/.s.PGSQL.' + this.port)
9098
} else {
@@ -161,6 +169,7 @@ Client.prototype._connect = function (callback) {
161169
return
162170
}
163171
this._connectionError = true
172+
clearTimeout(connectionTimeoutHandle)
164173
if (callback) {
165174
return callback(err)
166175
}
@@ -198,6 +207,7 @@ Client.prototype._connect = function (callback) {
198207
con.removeListener('errorMessage', connectingErrorHandler)
199208
con.on('error', connectedErrorHandler)
200209
con.on('errorMessage', connectedErrorMessageHandler)
210+
clearTimeout(connectionTimeoutHandle)
201211

202212
// process possible callback argument to Client#connect
203213
if (callback) {

Diff for: lib/connection-parameters.js

+19-2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,22 @@ var ConnectionParameters = function (config) {
6666
this.fallback_application_name = val('fallback_application_name', config, false)
6767
this.statement_timeout = val('statement_timeout', config, false)
6868
this.query_timeout = val('query_timeout', config, false)
69+
70+
if (config.connectionTimeoutMillis === undefined) {
71+
this.connect_timeout = process.env.PGCONNECT_TIMEOUT || 0
72+
} else {
73+
this.connect_timeout = Math.floor(config.connectionTimeoutMillis / 1000)
74+
}
75+
76+
if (config.keepAlive === false) {
77+
this.keepalives = 0
78+
} else if (config.keepAlive === true) {
79+
this.keepalives = 1
80+
}
81+
82+
if (typeof config.keepAliveInitialDelayMillis === 'number') {
83+
this.keepalives_idle = Math.floor(config.keepAliveInitialDelayMillis / 1000)
84+
}
6985
}
7086

7187
// Convert arg to a string, surround in single quotes, and escape single quotes and backslashes
@@ -75,7 +91,7 @@ var quoteParamValue = function (value) {
7591

7692
var add = function (params, config, paramName) {
7793
var value = config[paramName]
78-
if (value) {
94+
if (value !== undefined && value !== null) {
7995
params.push(paramName + '=' + quoteParamValue(value))
8096
}
8197
}
@@ -87,8 +103,9 @@ ConnectionParameters.prototype.getLibpqConnectionString = function (cb) {
87103
add(params, this, 'port')
88104
add(params, this, 'application_name')
89105
add(params, this, 'fallback_application_name')
106+
add(params, this, 'connect_timeout')
90107

91-
var ssl = typeof this.ssl === 'object' ? this.ssl : { sslmode: this.ssl }
108+
var ssl = typeof this.ssl === 'object' ? this.ssl : this.ssl ? { sslmode: this.ssl } : {}
92109
add(params, ssl, 'sslmode')
93110
add(params, ssl, 'sslca')
94111
add(params, ssl, 'sslkey')

Diff for: lib/connection.js

+2-12
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ var Connection = function (config) {
2121
config = config || {}
2222
this.stream = config.stream || new net.Socket()
2323
this._keepAlive = config.keepAlive
24-
this._keepAliveIdleMillis = config.keepAliveIdleMillis
25-
this._connectTimeoutMillis = config.connectTimeoutMillis
24+
this._keepAliveInitialDelayMillis = config.keepAliveInitialDelayMillis
2625
this.lastBuffer = false
2726
this.lastOffset = 0
2827
this.buffer = null
@@ -53,22 +52,13 @@ Connection.prototype.connect = function (port, host) {
5352

5453
if (this.stream.readyState === 'closed') {
5554
this.stream.connect(port, host)
56-
if (this._connectTimeoutMillis > 0) {
57-
this.stream.setTimeout(this._connectTimeoutMillis, function () {
58-
self._ending = true
59-
self.stream.destroy(new Error('Connection timeout expired'))
60-
})
61-
}
6255
} else if (this.stream.readyState === 'open') {
6356
this.emit('connect')
6457
}
6558

6659
this.stream.on('connect', function () {
6760
if (self._keepAlive) {
68-
self.stream.setKeepAlive(true, this._keepAliveIdleMillis)
69-
}
70-
if (self._connectTimeoutMillis > 0) {
71-
self.stream.setTimeout(0)
61+
self.stream.setKeepAlive(true, self._keepAliveInitialDelayMillis)
7262
}
7363
self.emit('connect')
7464
})

Diff for: lib/defaults.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,13 @@ module.exports = {
5858
statement_timeout: false,
5959

6060
// max miliseconds to wait for query to complete (client side)
61-
query_timeout: false
61+
query_timeout: false,
62+
63+
connect_timeout: 0,
64+
65+
keepalives: 1,
66+
67+
keepalives_idle: 0
6268
}
6369

6470
var pgTypes = require('pg-types')

Diff for: test/integration/client/connection-timeout-tests.js

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
'use strict'
2+
const net = require('net')
3+
const buffers = require('../../test-buffers')
4+
const helper = require('./test-helper')
5+
6+
const suite = new helper.Suite()
7+
8+
const options = {
9+
host: 'localhost',
10+
port: 54321,
11+
connectionTimeoutMillis: 2000,
12+
user: 'not',
13+
database: 'existing'
14+
}
15+
16+
const serverWithConnectionTimeout = (timeout, callback) => {
17+
const sockets = new Set()
18+
19+
const server = net.createServer(socket => {
20+
sockets.add(socket)
21+
socket.once('end', () => sockets.delete(socket))
22+
23+
socket.on('data', data => {
24+
// deny request for SSL
25+
if (data.length === 8) {
26+
socket.write(Buffer.from('N', 'utf8'))
27+
// consider all authentication requests as good
28+
} else if (!data[0]) {
29+
socket.write(buffers.authenticationOk())
30+
// send ReadyForQuery `timeout` ms after authentication
31+
setTimeout(() => socket.write(buffers.readyForQuery()), timeout).unref()
32+
// respond with our canned response
33+
} else {
34+
socket.write(buffers.readyForQuery())
35+
}
36+
})
37+
})
38+
39+
let closing = false
40+
const closeServer = done => {
41+
if (closing) return
42+
closing = true
43+
44+
server.close(done)
45+
for (const socket of sockets) {
46+
socket.destroy()
47+
}
48+
}
49+
50+
server.listen(options.port, options.host, () => callback(closeServer))
51+
}
52+
53+
suite.test('successful connection', done => {
54+
serverWithConnectionTimeout(0, closeServer => {
55+
const timeoutId = setTimeout(() => {
56+
throw new Error('Client should have connected successfully but it did not.')
57+
}, 3000)
58+
59+
const client = new helper.Client(options)
60+
client.connect()
61+
.then(() => client.end())
62+
.then(() => closeServer(done))
63+
.catch(err => closeServer(() => done(err)))
64+
.then(() => clearTimeout(timeoutId))
65+
})
66+
})
67+
68+
suite.test('expired connection timeout', done => {
69+
serverWithConnectionTimeout(options.connectionTimeoutMillis * 2, closeServer => {
70+
const timeoutId = setTimeout(() => {
71+
throw new Error('Client should have emitted an error but it did not.')
72+
}, 3000)
73+
74+
const client = new helper.Client(options)
75+
client.connect()
76+
.then(() => client.end())
77+
.then(() => closeServer(() => done(new Error('Connection timeout should have expired but it did not.'))))
78+
.catch(err => {
79+
assert(err instanceof Error)
80+
assert(/timeout expired\s*/.test(err.message))
81+
closeServer(done)
82+
})
83+
.then(() => clearTimeout(timeoutId))
84+
})
85+
})

Diff for: test/test-helper.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ var expect = function (callback, timeout) {
134134
assert.ok(executed,
135135
'Expected execution of function to be fired within ' + timeout +
136136
' milliseconds ' +
137-
+' (hint: export TEST_TIMEOUT=<timeout in milliseconds>' +
137+
' (hint: export TEST_TIMEOUT=<timeout in milliseconds>' +
138138
' to change timeout globally)' +
139139
callback.toString())
140140
}, timeout)

Diff for: test/unit/client/set-keepalives.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use strict'
2+
const net = require('net')
3+
const pg = require('../../../lib/index.js')
4+
const helper = require('./test-helper')
5+
6+
const suite = new helper.Suite()
7+
8+
suite.test('setting keep alive', done => {
9+
const server = net.createServer(c => {
10+
c.destroy()
11+
server.close()
12+
})
13+
14+
server.listen(7777, () => {
15+
const stream = new net.Socket()
16+
stream.setKeepAlive = (enable, initialDelay) => {
17+
assert(enable === true)
18+
assert(initialDelay === 10000)
19+
done()
20+
}
21+
22+
const client = new pg.Client({
23+
host: 'localhost',
24+
port: 7777,
25+
keepAlive: true,
26+
keepAliveInitialDelayMillis: 10000,
27+
stream
28+
})
29+
30+
client.connect().catch(() => {})
31+
})
32+
})

0 commit comments

Comments
 (0)