Skip to content

Commit 3ea8ff2

Browse files
ChrisG0x20ChrisWritable
authored andcommitted
Add connection lifetime limit option and tests
1 parent 97eea2d commit 3ea8ff2

File tree

2 files changed

+77
-0
lines changed

2 files changed

+77
-0
lines changed

Diff for: packages/pg-pool/index.js

+29
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class Pool extends EventEmitter {
8484
this.options.max = this.options.max || this.options.poolSize || 10
8585
this.options.maxUses = this.options.maxUses || Infinity
8686
this.options.allowExitOnIdle = this.options.allowExitOnIdle || false
87+
this.options.maxLifetimeSeconds = this.options.maxLifetimeSeconds || 0
8788
this.log = this.options.log || function () {}
8889
this.Client = this.options.Client || Client || require('pg').Client
8990
this.Promise = this.options.Promise || global.Promise
@@ -94,6 +95,7 @@ class Pool extends EventEmitter {
9495

9596
this._clients = []
9697
this._idle = []
98+
this._expired = new WeakSet()
9799
this._pendingQueue = []
98100
this._endCallback = undefined
99101
this.ending = false
@@ -123,6 +125,7 @@ class Pool extends EventEmitter {
123125
}
124126
return
125127
}
128+
126129
// if we don't have any waiting, do nothing
127130
if (!this._pendingQueue.length) {
128131
this.log('no queued requests')
@@ -248,6 +251,19 @@ class Pool extends EventEmitter {
248251
} else {
249252
this.log('new client connected')
250253

254+
if (0 !== this.options.maxLifetimeSeconds) {
255+
setTimeout(() => {
256+
this.log('ending client due to expired lifetime')
257+
this._expired.add(client)
258+
if (this._idle.length) {
259+
const idleIndex = this._idle.findIndex(idleItem => idleItem.client === client)
260+
if (-1 !== idleIndex) {
261+
this._acquireClient(client, new PendingItem((err, client, clientRelease) => clientRelease()), idleListener, false)
262+
}
263+
}
264+
}, this.options.maxLifetimeSeconds * 1000)
265+
}
266+
251267
return this._acquireClient(client, pendingItem, idleListener, true)
252268
}
253269
})
@@ -318,6 +334,15 @@ class Pool extends EventEmitter {
318334
return
319335
}
320336

337+
const isExpired = this._expired.has(client)
338+
if (isExpired) {
339+
this.log('remove expired client')
340+
this._expired.delete(client)
341+
this._remove(client)
342+
this._pulseQueue()
343+
return
344+
}
345+
321346
// idle timeout
322347
let tid
323348
if (this.options.idleTimeoutMillis) {
@@ -414,6 +439,10 @@ class Pool extends EventEmitter {
414439
return this._idle.length
415440
}
416441

442+
get expiredCount() {
443+
return this._clients.reduce((acc, client) => acc + (this._expired.has(client) ? 1 : 0), 0)
444+
}
445+
417446
get totalCount() {
418447
return this._clients.length
419448
}

Diff for: packages/pg-pool/test/lifetime-timeout.js

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use strict'
2+
const co = require('co')
3+
const expect = require('expect.js')
4+
5+
const describe = require('mocha').describe
6+
const it = require('mocha').it
7+
const path = require('path')
8+
9+
const Pool = require('../')
10+
11+
const wait = (time) => new Promise((resolve) => setTimeout(resolve, time))
12+
13+
describe('lifetime timeout', () => {
14+
it('connection lifetime should expire and remove the client', (done) => {
15+
const pool = new Pool({ maxLifetimeSeconds: 1 })
16+
pool.query('SELECT NOW()')
17+
pool.on('remove', () => {
18+
console.log('expired while idle - on-remove event')
19+
expect(pool.expiredCount).to.equal(0)
20+
expect(pool.totalCount).to.equal(0)
21+
done()
22+
})
23+
})
24+
it('connection lifetime should expire and remove the client after the client is done working', (done) => {
25+
const pool = new Pool({ maxLifetimeSeconds: 1 })
26+
pool.query('SELECT pg_sleep(1.01)')
27+
pool.on('remove', () => {
28+
console.log('expired while busy - on-remove event')
29+
expect(pool.expiredCount).to.equal(0)
30+
expect(pool.totalCount).to.equal(0)
31+
done()
32+
})
33+
})
34+
it('can remove expired clients and recreate them',
35+
co.wrap(function* () {
36+
const pool = new Pool({ maxLifetimeSeconds: 1 })
37+
let query = pool.query('SELECT pg_sleep(1)')
38+
expect(pool.expiredCount).to.equal(0)
39+
expect(pool.totalCount).to.equal(1)
40+
yield query
41+
expect(pool.expiredCount).to.equal(0)
42+
expect(pool.totalCount).to.equal(0)
43+
yield pool.query('SELECT NOW()')
44+
expect(pool.expiredCount).to.equal(0)
45+
expect(pool.totalCount).to.equal(1)
46+
})
47+
)
48+
})

0 commit comments

Comments
 (0)