Skip to content

Commit ab13d0c

Browse files
committed
initial crack at connection pooling -- still dirty
1 parent 5a87972 commit ab13d0c

File tree

10 files changed

+184
-14
lines changed

10 files changed

+184
-14
lines changed

lib/index.js

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,106 @@
11
var EventEmitter = require('events').EventEmitter;
22
var sys = require('sys');
33
var net = require('net');
4-
4+
var Pool = require(__dirname + '/utils').Pool;
55
var Client = require(__dirname+'/client');
6+
var defaults = require(__dirname + '/defaults');
7+
//connection pool global cache
8+
var clientPools = {
9+
}
10+
11+
var poolEnabled = function() {
12+
return defaults.poolSize;
13+
}
14+
15+
var log = function() {
16+
17+
}
18+
19+
var log = function() {
20+
console.log.apply(console, arguments);
21+
}
22+
23+
var getPooledClient = function(config, callback) {
24+
//lookup pool using config as key
25+
//TODO this don't work so hot w/ object configs
26+
var pool = clientPools[config];
27+
28+
//create pool if doesn't exist
29+
if(!pool) {
30+
log("creating pool %s", config)
31+
pool = clientPools[config] = new Pool(defaults.poolSize, function() {
32+
log("creating new client in pool %s", config)
33+
var client = new Client(config);
34+
client.connected = false;
35+
return client;
36+
})
37+
}
38+
39+
pool.checkOut(function(err, client) {
40+
//if client already connected just
41+
//pass it along to the callback and return
42+
if(client.connected) {
43+
callback(null, client);
44+
return;
45+
}
46+
47+
var onError = function(error) {
48+
client.connection.removeListener('readyForQuery', onReady);
49+
callback(error);
50+
pool.checkIn(client);
51+
}
52+
53+
var onReady = function() {
54+
client.removeListener('error', onError);
55+
client.connected = true;
56+
callback(null, client);
57+
client.on('drain', function() {
58+
pool.checkIn(client);
59+
});
60+
}
61+
62+
client.once('error', onError);
63+
64+
//TODO refactor
65+
//i don't like reaching into the client's connection for attaching
66+
//to specific events here
67+
client.connection.once('readyForQuery', onReady);
68+
69+
client.connect();
70+
71+
});
72+
}
73+
74+
//destroys the world
75+
var end = function(callback) {
76+
for(var name in clientPools) {
77+
var pool = clientPools[name];
78+
log("destroying pool %s", name);
79+
pool.waits.forEach(function(wait) {
80+
wait(new Error("Client is being destroyed"))
81+
})
82+
pool.waits = [];
83+
pool.items.forEach(function(item) {
84+
var client = item.ref;
85+
if(client.activeQuery) {
86+
log("client is still active, waiting for it to complete");
87+
client.on('drain', client.end.bind(client))
88+
} else {
89+
client.end();
90+
}
91+
})
92+
//remove reference to pool lookup
93+
clientPools[name] = null;
94+
delete(clientPools[name])
95+
}
96+
}
697

798
//wrap up common connection management boilerplate
899
var connect = function(config, callback) {
100+
if(poolEnabled()) {
101+
return getPooledClient(config, callback)
102+
}
103+
throw new Error("FUCK")
9104
var client = new Client(config);
10105
client.connect();
11106

@@ -32,5 +127,6 @@ module.exports = {
32127
Client: Client,
33128
Connection: require(__dirname + '/connection'),
34129
connect: connect,
35-
defaults: require(__dirname + '/defaults')
130+
end: end,
131+
defaults: defaults
36132
}

lib/utils.js

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
var events = require('events');
2+
var sys = require('sys');
23

34
if(typeof events.EventEmitter.prototype.once !== 'function') {
45
events.EventEmitter.prototype.once = function (type, listener) {
@@ -10,12 +11,13 @@ if(typeof events.EventEmitter.prototype.once !== 'function') {
1011
};
1112
}
1213
var Pool = function(maxSize, createFn) {
14+
events.EventEmitter.call(this);
1315
this.maxSize = maxSize;
1416
this.createFn = createFn;
1517
this.items = [];
1618
this.waits = [];
1719
}
18-
20+
sys.inherits(Pool, events.EventEmitter);
1921
var p = Pool.prototype;
2022

2123
p.checkOut = function(callback) {
@@ -27,10 +29,19 @@ p.checkOut = function(callback) {
2729
}
2830
}
2931
//check if we can create a new item
30-
if(len < this.maxSize && this.createFn) {
31-
var item = {ref: this.createFn()}
32+
if(this.items.length < this.maxSize && this.createFn) {
33+
var result = this.createFn();
34+
var item = result;
35+
//create function can return item conforming to interface
36+
//of stored items to allow for create function to create
37+
//checked out items
38+
if(typeof item.checkedIn == "undefined") {
39+
var item = {ref: result, checkedIn: true}
40+
}
3241
this.items.push(item);
33-
return this._pulse(item, callback)
42+
if(item.checkedIn) {
43+
return this._pulse(item, callback)
44+
}
3445
}
3546
this.waits.push(callback);
3647
return false; //did not execute sync
@@ -42,17 +53,19 @@ p.checkIn = function(item) {
4253
var currentItem = this.items[i];
4354
if(currentItem.ref == item) {
4455
currentItem.checkedIn = true;
45-
return this._pulse(currentItem);
56+
this._pulse(currentItem);
57+
return true;
4658
}
4759
}
4860
//add new item
4961
var newItem = {ref: item, checkedIn: true};
5062
this.items.push(newItem);
51-
return this._pulse(newItem);
63+
this._pulse(newItem);
64+
return false;
5265
}
5366

5467
p._pulse = function(item, cb) {
55-
cb = cb || this.waits.pop()
68+
cb = cb || this.waits.shift()
5669
if(cb) {
5770
item.checkedIn = false;
5871
cb(null, item.ref)

test/integration/client/api-tests.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ test('api', function() {
3636
}))
3737
})
3838

39-
4039
test('executing nested queries', function() {
4140
pg.connect(helper.args, assert.calls(function(err, client) {
4241
client.query('select now as now from NOW()', assert.calls(function(err, result) {
@@ -50,9 +49,11 @@ test('executing nested queries', function() {
5049
}))
5150
})
5251

53-
5452
test('raises error if cannot connect', function() {
55-
pg.connect({database:'asdlfkajsdf there is no way this is a real database, right?!'}, assert.calls(function(err, client) {
53+
var connectionString = "pg://asdf@seoiasfd:4884/ieieie";
54+
pg.connect(connectionString, assert.calls(function(err, client) {
5655
assert.ok(err, 'should have raised an error')
5756
}))
5857
})
58+
59+
pg.end();

test/integration/connection-pool/double-connection-tests.js

Whitespace-only changes.

test/integration/connection-pool/max-connection-tests.js

Whitespace-only changes.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
var helper = require(__dirname + "/test-helper")
2+
3+
setTimeout(function() {
4+
helper.pg.defaults.poolSize = 10;
5+
test('executes a single pooled connection/query', function() {
6+
var args = helper.args;
7+
var conString = "pg://"+args.user+":"+args.password+"@"+args.host+":"+args.port+"/"+args.database;
8+
var queryCount = 0;
9+
helper.pg.connect(conString, assert.calls(function(err, client) {
10+
assert.isNull(err);
11+
client.query("select * from NOW()", assert.calls(function(err, result) {
12+
assert.isNull(err);
13+
queryCount++;
14+
}))
15+
}))
16+
var id = setTimeout(function() {
17+
assert.equal(queryCount, 1)
18+
}, 1000)
19+
var check = function() {
20+
setTimeout(function() {
21+
if(queryCount == 1) {
22+
clearTimeout(id)
23+
helper.pg.end();
24+
} else {
25+
check();
26+
}
27+
}, 50)
28+
}
29+
check();
30+
})
31+
}, 1000)
32+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
args: require(__dirname + "/../test-helper").args,
3+
pg: require(__dirname + "/../../../lib")
4+
}

test/integration/connection-pool/waiting-connection-tests.js

Whitespace-only changes.

test/test-helper.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ var expect = function(callback, timeout) {
8686
var executed = false;
8787
var id = setTimeout(function() {
8888
assert.ok(executed, "Expected execution of " + callback + " fired");
89-
}, timeout || 1000)
89+
}, timeout || 2000)
9090

9191
return function(err, queryResult) {
9292
clearTimeout(id);

test/unit/utils-tests.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ test('an empty pool', function() {
6060
}))
6161
assert.ok(sync, "Should have generated item & called callback in sync")
6262
})
63-
63+
6464
test('creates again if item is checked out', function() {
6565
var sync = pool.checkOut(assert.calls(function(err, item) {
6666
assert.equal(item.name, "first2")
@@ -87,6 +87,30 @@ test('an empty pool', function() {
8787
pool.checkIn(external);
8888
})
8989
})
90+
})
91+
92+
test('when creating async new pool members', function() {
93+
var count = 0;
94+
var pool = new Pool(3, function() {
95+
var item = {ref: {name: ++count}, checkedIn: false};
96+
process.nextTick(function() {
97+
pool.checkIn(item.ref)
98+
})
99+
return item;
100+
})
101+
test('one request recieves member', function() {
102+
pool.checkOut(assert.calls(function(err, item) {
103+
assert.equal(item.name, 1)
104+
pool.checkOut(assert.calls(function(err, item) {
105+
assert.equal(item.name, 2)
106+
pool.checkOut(assert.calls(function(err, item) {
107+
assert.equal(item.name, 3)
108+
}))
109+
}))
110+
}))
111+
})
90112

91113
})
92114

115+
116+

0 commit comments

Comments
 (0)