Skip to content

Commit bb448fe

Browse files
committed
finish out the first rev of the improved pool api
1 parent 971eb5d commit bb448fe

File tree

3 files changed

+157
-19
lines changed

3 files changed

+157
-19
lines changed

lib/pool.js

+38-17
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,67 @@
1+
var EventEmitter = require('events').EventEmitter;
2+
13
var defaults = require(__dirname + '/defaults');
24
var genericPool = require('generic-pool');
35

46
//takes the same config structure as client
5-
var createPool = function(config) {
6-
config = config || {};
7-
var name = JSON.stringify(config);
7+
var createPool = function(clientConfig) {
8+
clientConfig = clientConfig || {};
9+
var name = JSON.stringify(clientConfig);
810
var pool = createPool.all[name];
911
if(pool) {
1012
return pool;
1113
}
1214
pool = genericPool.Pool({
1315
name: name,
1416
max: defaults.poolSize,
17+
idleTimeoutMillis: defaults.poolIdleTimeout,
18+
reapIntervalMillis: defaults.reapIntervalMillis,
19+
log: defaults.poolLog,
1520
create: function(cb) {
16-
var client = new createPool.Client(config);
21+
var client = new createPool.Client(clientConfig);
1722
client.connect(function(err) {
18-
return cb(err, client);
23+
if(err) return cb(err, null);
24+
25+
//handle connected client background errors by emitting event
26+
//via the pg object and then removing errored client from the pool
27+
client.on('error', function(e) {
28+
pool.emit('error', e, client);
29+
pool.destroy(client);
30+
});
31+
32+
return cb(null, client);
1933
});
2034
},
2135
destroy: function(client) {
2236
client.end();
2337
}
2438
});
2539
createPool.all[name] = pool;
40+
//mixin EventEmitter to pool
41+
EventEmitter.call(pool);
42+
for(var key in EventEmitter.prototype) {
43+
if(EventEmitter.prototype.hasOwnProperty(key)) {
44+
pool[key] = EventEmitter.prototype[key];
45+
}
46+
}
47+
//monkey-patch with connect method
2648
pool.connect = function(cb) {
2749
pool.acquire(function(err, client) {
28-
//TODO: on connection error should we remove this client automatically?
29-
if(err) {
30-
return cb(err);
31-
}
32-
if(cb.length > 2) {
33-
return newConnect(pool, client, cb);
34-
}
35-
return oldConnect(pool, client, cb);
50+
if(err) return cb(err, null, function() {/*NOOP*/});
51+
//support both 2 (old) and 3 arguments
52+
(cb.length > 2 ? newConnect : oldConnect)(pool, client, cb);
3653
});
3754
};
3855
return pool;
39-
}
56+
};
4057

4158
//the old connect method of the pool
4259
//would automatically subscribe to the 'drain'
4360
//event and automatically return the client to
4461
//the pool once 'drain' fired once. This caused
4562
//a bunch of problems, but for backwards compatibility
4663
//we're leaving it in
47-
var alarmDuration = 1000;
64+
var alarmDuration = 5000;
4865
var errorMessage = ['A client has been checked out from the pool for longer than ' + alarmDuration + ' ms.',
4966
'You might have a leak!',
5067
'You should use the following new way to check out clients','pg.connect(function(err, client, done)) {',
@@ -64,8 +81,12 @@ var oldConnect = function(pool, client, cb) {
6481
};
6582

6683
var newConnect = function(pool, client, cb) {
67-
cb(null, client, function() {
68-
pool.release(client);
84+
cb(null, client, function(err) {
85+
if(err) {
86+
pool.destroy(client);
87+
} else {
88+
pool.release(client);
89+
}
6990
});
7091
};
7192

test/unit/pool/basic-tests.js

+77-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ FakeClient.prototype.connect = function(cb) {
1919
}
2020

2121
FakeClient.prototype.end = function() {
22-
22+
this.endCalled = true;
2323
}
2424

2525
//Hangs the event loop until 'end' is called on client
@@ -59,7 +59,7 @@ test('pool creates pool on miss', function() {
5959
assert.equal(Object.keys(pool.all).length, 2);
6060
});
6161

62-
test('pool follows default limits', function() {
62+
test('pool follows defaults', function() {
6363
var p = pool(poolId++);
6464
for(var i = 0; i < 100; i++) {
6565
p.acquire(function(err, client) {
@@ -101,3 +101,78 @@ test('pool#connect with 3 parameters', function() {
101101
p.destroyAllNow();
102102
});
103103
});
104+
105+
test('on client error, client is removed from pool', function() {
106+
var p = pool(poolId++);
107+
p.connect(assert.success(function(client) {
108+
assert.ok(client);
109+
client.emit('drain');
110+
assert.equal(p.availableObjectsCount(), 1);
111+
assert.equal(p.getPoolSize(), 1);
112+
//error event fires on pool BEFORE pool.destroy is called with client
113+
assert.emits(p, 'error', function(err) {
114+
assert.equal(err.message, 'test error');
115+
assert.ok(!client.endCalled);
116+
assert.equal(p.availableObjectsCount(), 1);
117+
assert.equal(p.getPoolSize(), 1);
118+
//after we're done in our callback, pool.destroy is called
119+
process.nextTick(function() {
120+
assert.ok(client.endCalled);
121+
assert.equal(p.availableObjectsCount(), 0);
122+
assert.equal(p.getPoolSize(), 0);
123+
p.destroyAllNow();
124+
});
125+
});
126+
client.emit('error', new Error('test error'));
127+
}));
128+
});
129+
130+
test('pool with connection error on connection', function() {
131+
pool.Client = function() {
132+
return {
133+
connect: function(cb) {
134+
process.nextTick(function() {
135+
cb(new Error('Could not connect'));
136+
});
137+
}
138+
};
139+
}
140+
test('two parameters', function() {
141+
var p = pool(poolId++);
142+
p.connect(assert.calls(function(err, client) {
143+
assert.ok(err);
144+
assert.equal(client, null);
145+
//client automatically removed
146+
assert.equal(p.availableObjectsCount(), 0);
147+
assert.equal(p.getPoolSize(), 0);
148+
}));
149+
});
150+
test('three parameters', function() {
151+
var p = pool(poolId++);
152+
var tid = setTimeout(function() {
153+
assert.fail('Did not call connect callback');
154+
}, 100);
155+
p.connect(function(err, client, done) {
156+
clearTimeout(tid);
157+
assert.ok(err);
158+
assert.equal(client, null);
159+
//done does nothing
160+
done(new Error('OH NOOOO'));
161+
done();
162+
assert.equal(p.availableObjectsCount(), 0);
163+
assert.equal(p.getPoolSize(), 0);
164+
});
165+
});
166+
});
167+
168+
test('returnning an error to done()', function() {
169+
var p = pool(poolId++);
170+
pool.Client = FakeClient;
171+
p.connect(function(err, client, done) {
172+
assert.equal(err, null);
173+
assert(client);
174+
done(new Error("BROKEN"));
175+
assert.equal(p.availableObjectsCount(), 0);
176+
assert.equal(p.getPoolSize(), 0);
177+
});
178+
});

test/unit/pool/timeout-tests.js

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
var util = require('util');
2+
var EventEmitter = require('events').EventEmitter;
3+
4+
var libDir = __dirname + '/../../../lib';
5+
var defaults = require(libDir + '/defaults');
6+
var pool = require(libDir + '/pool');
7+
var poolId = 0;
8+
9+
require(__dirname + '/../../test-helper');
10+
11+
var FakeClient = function() {
12+
EventEmitter.call(this);
13+
}
14+
15+
util.inherits(FakeClient, EventEmitter);
16+
17+
FakeClient.prototype.connect = function(cb) {
18+
process.nextTick(cb);
19+
}
20+
21+
FakeClient.prototype.end = function() {
22+
this.endCalled = true;
23+
}
24+
25+
defaults.poolIdleTimeout = 10;
26+
defaults.reapIntervalMillis = 10;
27+
28+
test('client times out from idle', function() {
29+
pool.Client = FakeClient;
30+
var p = pool(poolId++);
31+
p.connect(function(err, client, done) {
32+
done();
33+
});
34+
process.nextTick(function() {
35+
assert.equal(p.availableObjectsCount(), 1);
36+
assert.equal(p.getPoolSize(), 1);
37+
setTimeout(function() {
38+
assert.equal(p.availableObjectsCount(), 0);
39+
assert.equal(p.getPoolSize(), 0);
40+
}, 50);
41+
});
42+
});

0 commit comments

Comments
 (0)