diff --git a/lib/pool.js b/lib/pool.js new file mode 100644 index 00000000..60e01c19 --- /dev/null +++ b/lib/pool.js @@ -0,0 +1,78 @@ +var defaults = require(__dirname + '/defaults'); +var genericPool = require('generic-pool'); + +//takes the same config structure as client +var createPool = function(config) { + config = config || {}; + var name = JSON.stringify(config); + var pool = createPool.all[name]; + if(pool) { + return pool; + } + pool = genericPool.Pool({ + name: name, + max: defaults.poolSize, + create: function(cb) { + var client = new createPool.Client(config); + client.connect(function(err) { + return cb(err, client); + }); + }, + destroy: function(client) { + client.end(); + } + }); + createPool.all[name] = pool; + pool.connect = function(cb) { + pool.acquire(function(err, client) { + //TODO: on connection error should we remove this client automatically? + if(err) { + return cb(err); + } + if(cb.length > 2) { + return newConnect(pool, client, cb); + } + return oldConnect(pool, client, cb); + }); + }; + return pool; +} + +//the old connect method of the pool +//would automatically subscribe to the 'drain' +//event and automatically return the client to +//the pool once 'drain' fired once. This caused +//a bunch of problems, but for backwards compatibility +//we're leaving it in +var alarmDuration = 1000; +var errorMessage = ['A client has been checked out from the pool for longer than ' + alarmDuration + ' ms.', +'You might have a leak!', +'You should use the following new way to check out clients','pg.connect(function(err, client, done)) {', +' //do something', +' done(); //call done() to signal you are finished with the client', +'}'].join(require('os').EOL); +var oldConnect = function(pool, client, cb) { + var tid = setTimeout(function() { + console.error(errorMessage); + }, alarmDuration); + var release = function() { + clearTimeout(tid); + pool.release(client); + }; + client.once('drain', release); + cb(null, client); +}; + +var newConnect = function(pool, client, cb) { + cb(null, client, function() { + pool.release(client); + }); +}; + +//list of all created pools +createPool.all = {}; + +//reference to client constructor +createPool.Client = require(__dirname + '/client'); + +module.exports = createPool; diff --git a/test/unit/pool/basic-tests.js b/test/unit/pool/basic-tests.js new file mode 100644 index 00000000..ac1071bd --- /dev/null +++ b/test/unit/pool/basic-tests.js @@ -0,0 +1,103 @@ +var util = require('util'); +var EventEmitter = require('events').EventEmitter; + +var libDir = __dirname + '/../../../lib'; +var defaults = require(libDir + '/defaults'); +var pool = require(libDir + '/pool'); +var poolId = 0; + +require(__dirname + '/../../test-helper'); + +var FakeClient = function() { + EventEmitter.call(this); +} + +util.inherits(FakeClient, EventEmitter); + +FakeClient.prototype.connect = function(cb) { + process.nextTick(cb); +} + +FakeClient.prototype.end = function() { + +} + +//Hangs the event loop until 'end' is called on client +var HangingClient = function(config) { + EventEmitter.call(this); + this.config = config; +} + +util.inherits(HangingClient, EventEmitter); + +HangingClient.prototype.connect = function(cb) { + this.intervalId = setInterval(function() { + console.log('hung client...'); + }, 1000); + process.nextTick(cb); +} + +HangingClient.prototype.end = function() { + clearInterval(this.intervalId); +} + +pool.Client = FakeClient; + +test('no pools exist', function() { + assert.empty(Object.keys(pool.all)); +}); + +test('pool creates pool on miss', function() { + var p = pool(); + assert.ok(p); + assert.equal(Object.keys(pool.all).length, 1); + var p2 = pool(); + assert.equal(p, p2); + assert.equal(Object.keys(pool.all).length, 1); + var p3 = pool("pg://postgres:password@localhost:5432/postgres"); + assert.notEqual(p, p3); + assert.equal(Object.keys(pool.all).length, 2); +}); + +test('pool follows default limits', function() { + var p = pool(poolId++); + for(var i = 0; i < 100; i++) { + p.acquire(function(err, client) { + }); + } + assert.equal(p.getPoolSize(), defaults.poolSize); +}); + +test('pool#connect with 2 parameters (legacy, for backwards compat)', function() { + var p = pool(poolId++); + p.connect(assert.success(function(client) { + assert.ok(client); + assert.equal(p.availableObjectsCount(), 0); + assert.equal(p.getPoolSize(), 1); + client.emit('drain'); + assert.equal(p.availableObjectsCount(), 1); + assert.equal(p.getPoolSize(), 1); + p.destroyAllNow(); + })); +}); + +test('pool#connect with 3 parameters', function() { + var p = pool(poolId++); + var tid = setTimeout(function() { + throw new Error("Connection callback was never called"); + }, 100); + p.connect(function(err, client, done) { + clearTimeout(tid); + assert.equal(err, null); + assert.ok(client); + assert.equal(p.availableObjectsCount(), 0); + assert.equal(p.getPoolSize(), 1); + client.emit('drain'); + assert.equal(p.availableObjectsCount(), 0); + assert.equal(p.getPoolSize(), 1); + done(); + assert.equal(p.availableObjectsCount(), 1); + assert.equal(p.getPoolSize(), 1); + p.destroyAllNow(); + }); +});