Remove internal pool (#1049)
* Initial work on removing internal pool * Port backwards-compabible properties * Cleanup test execution & makefile cruft * Attempt to fix flakey error testremotes/origin/cdb-6.1
parent
1596a933eb
commit
796a44f54f
@ -1,99 +0,0 @@
|
|||||||
var EventEmitter = require('events').EventEmitter;
|
|
||||||
|
|
||||||
var defaults = require('./defaults');
|
|
||||||
var genericPool = require('generic-pool');
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = function(Client) {
|
|
||||||
var pools = {
|
|
||||||
Client: Client,
|
|
||||||
//dictionary of all key:pool pairs
|
|
||||||
all: {},
|
|
||||||
//reference to the client constructor - can override in tests or for require('pg').native
|
|
||||||
getOrCreate: function(clientConfig) {
|
|
||||||
clientConfig = clientConfig || {};
|
|
||||||
var name = JSON.stringify(clientConfig);
|
|
||||||
var pool = pools.all[name];
|
|
||||||
if(pool) {
|
|
||||||
return pool;
|
|
||||||
}
|
|
||||||
pool = genericPool.Pool({
|
|
||||||
name: name,
|
|
||||||
max: clientConfig.poolSize || defaults.poolSize,
|
|
||||||
idleTimeoutMillis: clientConfig.poolIdleTimeout || defaults.poolIdleTimeout,
|
|
||||||
reapIntervalMillis: clientConfig.reapIntervalMillis || defaults.reapIntervalMillis,
|
|
||||||
returnToHead: clientConfig.returnToHead || defaults.returnToHead,
|
|
||||||
log: clientConfig.poolLog || defaults.poolLog,
|
|
||||||
create: function(cb) {
|
|
||||||
var client = new pools.Client(clientConfig);
|
|
||||||
// Ignore errors on pooled clients until they are connected.
|
|
||||||
client.on('error', Function.prototype);
|
|
||||||
client.connect(function(err) {
|
|
||||||
if(err) return cb(err, null);
|
|
||||||
|
|
||||||
// Remove the noop error handler after a connection has been established.
|
|
||||||
client.removeListener('error', Function.prototype);
|
|
||||||
|
|
||||||
//handle connected client background errors by emitting event
|
|
||||||
//via the pg object and then removing errored client from the pool
|
|
||||||
client.on('error', function(e) {
|
|
||||||
pool.emit('error', e, client);
|
|
||||||
|
|
||||||
// If the client is already being destroyed, the error
|
|
||||||
// occurred during stream ending. Do not attempt to destroy
|
|
||||||
// the client again.
|
|
||||||
if (!client._destroying) {
|
|
||||||
pool.destroy(client);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove connection from pool on disconnect
|
|
||||||
client.on('end', function(e) {
|
|
||||||
// Do not enter infinite loop between pool.destroy
|
|
||||||
// and client 'end' event...
|
|
||||||
if ( ! client._destroying ) {
|
|
||||||
pool.destroy(client);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
client.poolCount = 0;
|
|
||||||
return cb(null, client);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
destroy: function(client) {
|
|
||||||
client._destroying = true;
|
|
||||||
client.poolCount = undefined;
|
|
||||||
client.end();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
pools.all[name] = pool;
|
|
||||||
//mixin EventEmitter to pool
|
|
||||||
EventEmitter.call(pool);
|
|
||||||
for(var key in EventEmitter.prototype) {
|
|
||||||
if(EventEmitter.prototype.hasOwnProperty(key)) {
|
|
||||||
pool[key] = EventEmitter.prototype[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//monkey-patch with connect method
|
|
||||||
pool.connect = function(cb) {
|
|
||||||
var domain = process.domain;
|
|
||||||
pool.acquire(function(err, client) {
|
|
||||||
if(domain) {
|
|
||||||
cb = domain.bind(cb);
|
|
||||||
}
|
|
||||||
if(err) return cb(err, null, function() {/*NOOP*/});
|
|
||||||
client.poolCount++;
|
|
||||||
cb(null, client, function(err) {
|
|
||||||
if(err) {
|
|
||||||
pool.destroy(client);
|
|
||||||
} else {
|
|
||||||
pool.release(client);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
return pool;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return pools;
|
|
||||||
};
|
|
@ -1,24 +0,0 @@
|
|||||||
var helper = require(__dirname + '/../test/test-helper');
|
|
||||||
|
|
||||||
console.log();
|
|
||||||
console.log("testing ability to connect to '%j'", helper.config);
|
|
||||||
var pg = require(__dirname + '/../lib');
|
|
||||||
pg.connect(helper.config, function(err, client, done) {
|
|
||||||
if(err !== null) {
|
|
||||||
console.error("Recieved connection error when attempting to contact PostgreSQL:");
|
|
||||||
console.error(err);
|
|
||||||
process.exit(255);
|
|
||||||
}
|
|
||||||
console.log("Checking for existance of required test table 'person'")
|
|
||||||
client.query("SELECT COUNT(name) FROM person", function(err, callback) {
|
|
||||||
if(err != null) {
|
|
||||||
console.error("Recieved error when executing query 'SELECT COUNT(name) FROM person'")
|
|
||||||
console.error("It is possible you have not yet run the table create script under script/create-test-tables")
|
|
||||||
console.error("Consult the postgres-node wiki under the 'Testing' section for more information")
|
|
||||||
console.error(err);
|
|
||||||
process.exit(255);
|
|
||||||
}
|
|
||||||
done();
|
|
||||||
pg.end();
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,34 +0,0 @@
|
|||||||
return console.log('query-callback-error-tests: DEPRECATED - if you want safety in your callback, you can try/catch your own functions');
|
|
||||||
var helper = require(__dirname + '/test-helper');
|
|
||||||
var util = require('util');
|
|
||||||
|
|
||||||
var withQuery = function(text, resultLength, cb) {
|
|
||||||
test('error during query execution', function() {
|
|
||||||
var client = new Client(helper.args);
|
|
||||||
process.removeAllListeners('uncaughtException');
|
|
||||||
assert.emits(process, 'uncaughtException', function() {
|
|
||||||
assert.equal(client.activeQuery, null, 'should remove active query even if error happens in callback');
|
|
||||||
client.query('SELECT * FROM blah', assert.success(function(result) {
|
|
||||||
assert.equal(result.rows.length, resultLength);
|
|
||||||
client.end();
|
|
||||||
cb();
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
client.connect(assert.success(function() {
|
|
||||||
client.query('CREATE TEMP TABLE "blah"(data text)', assert.success(function() {
|
|
||||||
var q = client.query(text, ['yo'], assert.calls(function() {
|
|
||||||
assert.emits(client, 'drain');
|
|
||||||
throw new Error('WHOOOAAAHH!!');
|
|
||||||
}));
|
|
||||||
}));
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//test with good query so our callback is called
|
|
||||||
//as a successful callback
|
|
||||||
withQuery('INSERT INTO blah(data) VALUES($1)', 1, function() {
|
|
||||||
//test with an error query so our callback is called with an error
|
|
||||||
withQuery('INSERT INTO asldkfjlaskfj eoooeoriiri', 0, function() {
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,214 +0,0 @@
|
|||||||
var util = require('util');
|
|
||||||
var EventEmitter = require('events').EventEmitter;
|
|
||||||
|
|
||||||
var libDir = __dirname + '/../../../lib';
|
|
||||||
var poolsFactory = require(libDir + '/pool')
|
|
||||||
var defaults = require(libDir + '/defaults');
|
|
||||||
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() {
|
|
||||||
this.endCalled = true;
|
|
||||||
}
|
|
||||||
var pools = poolsFactory(FakeClient);
|
|
||||||
|
|
||||||
//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);
|
|
||||||
}
|
|
||||||
|
|
||||||
test('no pools exist', function() {
|
|
||||||
assert.empty(Object.keys(pools.all));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('pool creates pool on miss', function() {
|
|
||||||
var p = pools.getOrCreate();
|
|
||||||
assert.ok(p);
|
|
||||||
assert.equal(Object.keys(pools.all).length, 1);
|
|
||||||
var p2 = pools.getOrCreate();
|
|
||||||
assert.equal(p, p2);
|
|
||||||
assert.equal(Object.keys(pools.all).length, 1);
|
|
||||||
var p3 = pools.getOrCreate("postgres://postgres:password@localhost:5432/postgres");
|
|
||||||
assert.notEqual(p, p3);
|
|
||||||
assert.equal(Object.keys(pools.all).length, 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('pool follows defaults', function() {
|
|
||||||
var p = pools.getOrCreate(poolId++);
|
|
||||||
for(var i = 0; i < 100; i++) {
|
|
||||||
p.acquire(function(err, client) {
|
|
||||||
});
|
|
||||||
}
|
|
||||||
assert.equal(p.getPoolSize(), defaults.poolSize);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('pool#connect with 3 parameters', function() {
|
|
||||||
var p = pools.getOrCreate(poolId++);
|
|
||||||
var tid = setTimeout(function() {
|
|
||||||
throw new Error("Connection callback was never called");
|
|
||||||
}, 100);
|
|
||||||
p.connect(function(err, client, done) {
|
|
||||||
clearTimeout(tid);
|
|
||||||
assert.ifError(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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('on client error, client is removed from pool', function() {
|
|
||||||
var p = pools.getOrCreate(poolId++);
|
|
||||||
p.connect(assert.success(function(client, done) {
|
|
||||||
assert.ok(client);
|
|
||||||
done();
|
|
||||||
assert.equal(p.availableObjectsCount(), 1);
|
|
||||||
assert.equal(p.getPoolSize(), 1);
|
|
||||||
//error event fires on pool BEFORE pool.destroy is called with client
|
|
||||||
assert.emits(p, 'error', function(err) {
|
|
||||||
assert.equal(err.message, 'test error');
|
|
||||||
assert.ok(!client.endCalled);
|
|
||||||
assert.equal(p.availableObjectsCount(), 1);
|
|
||||||
assert.equal(p.getPoolSize(), 1);
|
|
||||||
//after we're done in our callback, pool.destroy is called
|
|
||||||
process.nextTick(function() {
|
|
||||||
assert.ok(client.endCalled);
|
|
||||||
assert.equal(p.availableObjectsCount(), 0);
|
|
||||||
assert.equal(p.getPoolSize(), 0);
|
|
||||||
p.destroyAllNow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
client.emit('error', new Error('test error'));
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('pool with connection error on connection', function() {
|
|
||||||
var errorPools = poolsFactory(function() {
|
|
||||||
return {
|
|
||||||
connect: function(cb) {
|
|
||||||
process.nextTick(function() {
|
|
||||||
cb(new Error('Could not connect'));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
on: Function.prototype
|
|
||||||
};
|
|
||||||
})
|
|
||||||
|
|
||||||
test('two parameters', function() {
|
|
||||||
var p = errorPools.getOrCreate(poolId++);
|
|
||||||
p.connect(assert.calls(function(err, client) {
|
|
||||||
assert.ok(err);
|
|
||||||
assert.equal(client, null);
|
|
||||||
//client automatically removed
|
|
||||||
assert.equal(p.availableObjectsCount(), 0);
|
|
||||||
assert.equal(p.getPoolSize(), 0);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
test('three parameters', function() {
|
|
||||||
var p = errorPools.getOrCreate(poolId++);
|
|
||||||
var tid = setTimeout(function() {
|
|
||||||
assert.fail('Did not call connect callback');
|
|
||||||
}, 100);
|
|
||||||
p.connect(function(err, client, done) {
|
|
||||||
clearTimeout(tid);
|
|
||||||
assert.ok(err);
|
|
||||||
assert.equal(client, null);
|
|
||||||
//done does nothing
|
|
||||||
done(new Error('OH NOOOO'));
|
|
||||||
done();
|
|
||||||
assert.equal(p.availableObjectsCount(), 0);
|
|
||||||
assert.equal(p.getPoolSize(), 0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('returnning an error to done()', function() {
|
|
||||||
var p = pools.getOrCreate(poolId++);
|
|
||||||
p.connect(function(err, client, done) {
|
|
||||||
assert.equal(err, null);
|
|
||||||
assert(client);
|
|
||||||
done(new Error("BROKEN"));
|
|
||||||
assert.equal(p.availableObjectsCount(), 0);
|
|
||||||
assert.equal(p.getPoolSize(), 0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fetching pool by object', function() {
|
|
||||||
var p = pools.getOrCreate({
|
|
||||||
user: 'brian',
|
|
||||||
host: 'localhost',
|
|
||||||
password: 'password'
|
|
||||||
});
|
|
||||||
var p2 = pools.getOrCreate({
|
|
||||||
user: 'brian',
|
|
||||||
host: 'localhost',
|
|
||||||
password: 'password'
|
|
||||||
});
|
|
||||||
assert.equal(p, p2);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
test('pool#connect client.poolCount', function() {
|
|
||||||
var p = pools.getOrCreate(poolId++);
|
|
||||||
var tid;
|
|
||||||
|
|
||||||
setConnectTimeout = function() {
|
|
||||||
tid = setTimeout(function() {
|
|
||||||
throw new Error("Connection callback was never called");
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
setConnectTimeout();
|
|
||||||
p.connect(function(err, client, done) {
|
|
||||||
clearTimeout(tid);
|
|
||||||
assert.equal(client.poolCount, 1,
|
|
||||||
'after connect, poolCount should be 1');
|
|
||||||
done();
|
|
||||||
assert.equal(client.poolCount, 1,
|
|
||||||
'after returning client to pool, poolCount should still be 1');
|
|
||||||
setConnectTimeout();
|
|
||||||
p.connect(function(err, client, done) {
|
|
||||||
clearTimeout(tid);
|
|
||||||
assert.equal(client.poolCount, 2,
|
|
||||||
'after second connect, poolCount should be 2');
|
|
||||||
done();
|
|
||||||
setConnectTimeout();
|
|
||||||
p.destroyAllNow(function() {
|
|
||||||
clearTimeout(tid);
|
|
||||||
assert.equal(client.poolCount, undefined,
|
|
||||||
'after pool is destroyed, count should be undefined');
|
|
||||||
});
|
|
||||||
})
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,43 +0,0 @@
|
|||||||
var util = require('util');
|
|
||||||
var EventEmitter = require('events').EventEmitter;
|
|
||||||
|
|
||||||
var libDir = __dirname + '/../../../lib';
|
|
||||||
var defaults = require(libDir + '/defaults');
|
|
||||||
var poolsFactory = 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() {
|
|
||||||
this.endCalled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
defaults.poolIdleTimeout = 10;
|
|
||||||
defaults.reapIntervalMillis = 10;
|
|
||||||
|
|
||||||
var pools = poolsFactory(FakeClient)
|
|
||||||
|
|
||||||
test('client times out from idle', function() {
|
|
||||||
var p = pools.getOrCreate(poolId++);
|
|
||||||
p.connect(function(err, client, done) {
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
process.nextTick(function() {
|
|
||||||
assert.equal(p.availableObjectsCount(), 1);
|
|
||||||
assert.equal(p.getPoolSize(), 1);
|
|
||||||
setTimeout(function() {
|
|
||||||
assert.equal(p.availableObjectsCount(), 0);
|
|
||||||
assert.equal(p.getPoolSize(), 0);
|
|
||||||
}, 50);
|
|
||||||
});
|
|
||||||
});
|
|
Loading…
Reference in new issue