Requiring native bindings polutes 'global' (#984)
There was some nasty global-ish variable reference updating happening when the native module 'initializes' after its require with `require('pg').native` This fixes the issue by making sure both `require('pg')` and `require('pg').native` each initialize their own context in isolation and no weird global-ish references are used & subsequently stomped on.
This commit is contained in:
parent
beb8eef1f4
commit
a8bd44a6ec
@ -8,9 +8,9 @@ var Connection = require('./connection');
|
|||||||
var PG = function(clientConstructor) {
|
var PG = function(clientConstructor) {
|
||||||
EventEmitter.call(this);
|
EventEmitter.call(this);
|
||||||
this.defaults = defaults;
|
this.defaults = defaults;
|
||||||
this.Client = pool.Client = clientConstructor;
|
this.Client = clientConstructor;
|
||||||
this.Query = this.Client.Query;
|
this.Query = this.Client.Query;
|
||||||
this.pools = pool;
|
this.pools = pool(clientConstructor);
|
||||||
this.Connection = Connection;
|
this.Connection = Connection;
|
||||||
this.types = require('pg-types');
|
this.types = require('pg-types');
|
||||||
};
|
};
|
||||||
|
174
lib/pool.js
174
lib/pool.js
@ -3,93 +3,95 @@ var EventEmitter = require('events').EventEmitter;
|
|||||||
var defaults = require('./defaults');
|
var defaults = require('./defaults');
|
||||||
var genericPool = require('generic-pool');
|
var genericPool = require('generic-pool');
|
||||||
|
|
||||||
var pools = {
|
|
||||||
//dictionary of all key:pool pairs
|
module.exports = function(Client) {
|
||||||
all: {},
|
var pools = {
|
||||||
//reference to the client constructor - can override in tests or for require('pg').native
|
//dictionary of all key:pool pairs
|
||||||
Client: require('./client'),
|
all: {},
|
||||||
getOrCreate: function(clientConfig) {
|
//reference to the client constructor - can override in tests or for require('pg').native
|
||||||
clientConfig = clientConfig || {};
|
getOrCreate: function(clientConfig) {
|
||||||
var name = JSON.stringify(clientConfig);
|
clientConfig = clientConfig || {};
|
||||||
var pool = pools.all[name];
|
var name = JSON.stringify(clientConfig);
|
||||||
if(pool) {
|
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,
|
||||||
|
log: clientConfig.poolLog || defaults.poolLog,
|
||||||
|
create: function(cb) {
|
||||||
|
var client = new 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 pool;
|
||||||
}
|
}
|
||||||
pool = genericPool.Pool({
|
};
|
||||||
name: name,
|
|
||||||
max: clientConfig.poolSize || defaults.poolSize,
|
|
||||||
idleTimeoutMillis: clientConfig.poolIdleTimeout || defaults.poolIdleTimeout,
|
|
||||||
reapIntervalMillis: clientConfig.reapIntervalMillis || defaults.reapIntervalMillis,
|
|
||||||
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.
|
return pools;
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = pools;
|
|
||||||
|
27
test/integration/gh-issues/981-tests.js
Normal file
27
test/integration/gh-issues/981-tests.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
var helper = require(__dirname + '/../test-helper');
|
||||||
|
|
||||||
|
//native bindings are only installed for native tests
|
||||||
|
if(!helper.args.native) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var assert = require('assert')
|
||||||
|
var pg = require('../../../lib')
|
||||||
|
var native = require('../../../lib').native
|
||||||
|
|
||||||
|
var JsClient = require('../../../lib/client')
|
||||||
|
var NativeClient = require('../../../lib/native')
|
||||||
|
|
||||||
|
assert(pg.Client === JsClient);
|
||||||
|
assert(native.Client === NativeClient);
|
||||||
|
|
||||||
|
pg.connect(function(err, client, done) {
|
||||||
|
assert(client instanceof JsClient);
|
||||||
|
client.end();
|
||||||
|
|
||||||
|
native.connect(function(err, client, done) {
|
||||||
|
assert(client instanceof NativeClient);
|
||||||
|
client.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -2,8 +2,8 @@ var util = require('util');
|
|||||||
var EventEmitter = require('events').EventEmitter;
|
var EventEmitter = require('events').EventEmitter;
|
||||||
|
|
||||||
var libDir = __dirname + '/../../../lib';
|
var libDir = __dirname + '/../../../lib';
|
||||||
|
var poolsFactory = require(libDir + '/pool')
|
||||||
var defaults = require(libDir + '/defaults');
|
var defaults = require(libDir + '/defaults');
|
||||||
var pools = require(libDir + '/pool');
|
|
||||||
var poolId = 0;
|
var poolId = 0;
|
||||||
|
|
||||||
require(__dirname + '/../../test-helper');
|
require(__dirname + '/../../test-helper');
|
||||||
@ -21,6 +21,7 @@ FakeClient.prototype.connect = function(cb) {
|
|||||||
FakeClient.prototype.end = function() {
|
FakeClient.prototype.end = function() {
|
||||||
this.endCalled = true;
|
this.endCalled = true;
|
||||||
}
|
}
|
||||||
|
var pools = poolsFactory(FakeClient);
|
||||||
|
|
||||||
//Hangs the event loop until 'end' is called on client
|
//Hangs the event loop until 'end' is called on client
|
||||||
var HangingClient = function(config) {
|
var HangingClient = function(config) {
|
||||||
@ -41,8 +42,6 @@ HangingClient.prototype.end = function() {
|
|||||||
clearInterval(this.intervalId);
|
clearInterval(this.intervalId);
|
||||||
}
|
}
|
||||||
|
|
||||||
pools.Client = FakeClient;
|
|
||||||
|
|
||||||
test('no pools exist', function() {
|
test('no pools exist', function() {
|
||||||
assert.empty(Object.keys(pools.all));
|
assert.empty(Object.keys(pools.all));
|
||||||
});
|
});
|
||||||
@ -115,7 +114,7 @@ test('on client error, client is removed from pool', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('pool with connection error on connection', function() {
|
test('pool with connection error on connection', function() {
|
||||||
pools.Client = function() {
|
var errorPools = poolsFactory(function() {
|
||||||
return {
|
return {
|
||||||
connect: function(cb) {
|
connect: function(cb) {
|
||||||
process.nextTick(function() {
|
process.nextTick(function() {
|
||||||
@ -124,9 +123,10 @@ test('pool with connection error on connection', function() {
|
|||||||
},
|
},
|
||||||
on: Function.prototype
|
on: Function.prototype
|
||||||
};
|
};
|
||||||
};
|
})
|
||||||
|
|
||||||
test('two parameters', function() {
|
test('two parameters', function() {
|
||||||
var p = pools.getOrCreate(poolId++);
|
var p = errorPools.getOrCreate(poolId++);
|
||||||
p.connect(assert.calls(function(err, client) {
|
p.connect(assert.calls(function(err, client) {
|
||||||
assert.ok(err);
|
assert.ok(err);
|
||||||
assert.equal(client, null);
|
assert.equal(client, null);
|
||||||
@ -136,7 +136,7 @@ test('pool with connection error on connection', function() {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
test('three parameters', function() {
|
test('three parameters', function() {
|
||||||
var p = pools.getOrCreate(poolId++);
|
var p = errorPools.getOrCreate(poolId++);
|
||||||
var tid = setTimeout(function() {
|
var tid = setTimeout(function() {
|
||||||
assert.fail('Did not call connect callback');
|
assert.fail('Did not call connect callback');
|
||||||
}, 100);
|
}, 100);
|
||||||
@ -155,7 +155,6 @@ test('pool with connection error on connection', function() {
|
|||||||
|
|
||||||
test('returnning an error to done()', function() {
|
test('returnning an error to done()', function() {
|
||||||
var p = pools.getOrCreate(poolId++);
|
var p = pools.getOrCreate(poolId++);
|
||||||
pools.Client = FakeClient;
|
|
||||||
p.connect(function(err, client, done) {
|
p.connect(function(err, client, done) {
|
||||||
assert.equal(err, null);
|
assert.equal(err, null);
|
||||||
assert(client);
|
assert(client);
|
||||||
|
@ -3,7 +3,7 @@ var EventEmitter = require('events').EventEmitter;
|
|||||||
|
|
||||||
var libDir = __dirname + '/../../../lib';
|
var libDir = __dirname + '/../../../lib';
|
||||||
var defaults = require(libDir + '/defaults');
|
var defaults = require(libDir + '/defaults');
|
||||||
var pools = require(libDir + '/pool');
|
var poolsFactory = require(libDir + '/pool');
|
||||||
var poolId = 0;
|
var poolId = 0;
|
||||||
|
|
||||||
require(__dirname + '/../../test-helper');
|
require(__dirname + '/../../test-helper');
|
||||||
@ -25,8 +25,9 @@ FakeClient.prototype.end = function() {
|
|||||||
defaults.poolIdleTimeout = 10;
|
defaults.poolIdleTimeout = 10;
|
||||||
defaults.reapIntervalMillis = 10;
|
defaults.reapIntervalMillis = 10;
|
||||||
|
|
||||||
|
var pools = poolsFactory(FakeClient)
|
||||||
|
|
||||||
test('client times out from idle', function() {
|
test('client times out from idle', function() {
|
||||||
pools.Client = FakeClient;
|
|
||||||
var p = pools.getOrCreate(poolId++);
|
var p = pools.getOrCreate(poolId++);
|
||||||
p.connect(function(err, client, done) {
|
p.connect(function(err, client, done) {
|
||||||
done();
|
done();
|
||||||
|
Loading…
Reference in New Issue
Block a user