Skip to content

Commit e4e6713

Browse files
committed
Add connection lifetime limit option and tests
1 parent 97eea2d commit e4e6713

File tree

2 files changed

+92
-0
lines changed

2 files changed

+92
-0
lines changed

packages/pg-pool/index.js

100644100755
+44
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 = []
9799
this._pendingQueue = []
98100
this._endCallback = undefined
99101
this.ending = false
@@ -123,6 +125,24 @@ class Pool extends EventEmitter {
123125
}
124126
return
125127
}
128+
129+
// if there are idle clients; see if any lifetimes have expired
130+
if (this._idle.length && this._expired.length) {
131+
let newlyExpired = []
132+
this._idle.forEach((idleItem) => {
133+
const client = idleItem.client
134+
const expiredIndex = this._expired.findIndex(c => c === client)
135+
if (-1 !== expiredIndex) {
136+
newlyExpired.push(expiredIndex)
137+
}
138+
})
139+
newlyExpired.forEach((expiredIndex) => {
140+
this.log('remove expired client')
141+
const expiredClient = this._expired.splice(expiredIndex, 1)[0]
142+
this._remove(expiredClient)
143+
})
144+
}
145+
126146
// if we don't have any waiting, do nothing
127147
if (!this._pendingQueue.length) {
128148
this.log('no queued requests')
@@ -248,6 +268,17 @@ class Pool extends EventEmitter {
248268
} else {
249269
this.log('new client connected')
250270

271+
if (0 !== this.options.maxLifetimeSeconds) {
272+
setTimeout(() => {
273+
const expiredClient = this._clients.findIndex(c => c === client)
274+
if (-1 !== expiredClient) {
275+
this.log('ending client due to expired lifetime')
276+
this._expired.push(client)
277+
this._pulseQueue()
278+
}
279+
}, this.options.maxLifetimeSeconds * 1000)
280+
}
281+
251282
return this._acquireClient(client, pendingItem, idleListener, true)
252283
}
253284
})
@@ -318,6 +349,15 @@ class Pool extends EventEmitter {
318349
return
319350
}
320351

352+
const expiredClient = this._expired.findIndex(c => c === client)
353+
if (-1 !== expiredClient) {
354+
this.log('remove expired client')
355+
this._expired.splice(expiredClient, 1)
356+
this._remove(client)
357+
this._pulseQueue()
358+
return
359+
}
360+
321361
// idle timeout
322362
let tid
323363
if (this.options.idleTimeoutMillis) {
@@ -414,6 +454,10 @@ class Pool extends EventEmitter {
414454
return this._idle.length
415455
}
416456

457+
get expiredCount() {
458+
return this._expired.length
459+
}
460+
417461
get totalCount() {
418462
return this._clients.length
419463
}
+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)