Skip to content

Commit a8bd44a

Browse files
committed
Requiring native bindings polutes 'global' (brianc#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.
1 parent beb8eef commit a8bd44a

File tree

5 files changed

+121
-92
lines changed

5 files changed

+121
-92
lines changed

lib/index.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ var Connection = require('./connection');
88
var PG = function(clientConstructor) {
99
EventEmitter.call(this);
1010
this.defaults = defaults;
11-
this.Client = pool.Client = clientConstructor;
11+
this.Client = clientConstructor;
1212
this.Query = this.Client.Query;
13-
this.pools = pool;
13+
this.pools = pool(clientConstructor);
1414
this.Connection = Connection;
1515
this.types = require('pg-types');
1616
};

lib/pool.js

+82-80
Original file line numberDiff line numberDiff line change
@@ -3,93 +3,95 @@ var EventEmitter = require('events').EventEmitter;
33
var defaults = require('./defaults');
44
var genericPool = require('generic-pool');
55

6-
var pools = {
7-
//dictionary of all key:pool pairs
8-
all: {},
9-
//reference to the client constructor - can override in tests or for require('pg').native
10-
Client: require('./client'),
11-
getOrCreate: function(clientConfig) {
12-
clientConfig = clientConfig || {};
13-
var name = JSON.stringify(clientConfig);
14-
var pool = pools.all[name];
15-
if(pool) {
16-
return pool;
17-
}
18-
pool = genericPool.Pool({
19-
name: name,
20-
max: clientConfig.poolSize || defaults.poolSize,
21-
idleTimeoutMillis: clientConfig.poolIdleTimeout || defaults.poolIdleTimeout,
22-
reapIntervalMillis: clientConfig.reapIntervalMillis || defaults.reapIntervalMillis,
23-
log: clientConfig.poolLog || defaults.poolLog,
24-
create: function(cb) {
25-
var client = new pools.Client(clientConfig);
26-
// Ignore errors on pooled clients until they are connected.
27-
client.on('error', Function.prototype);
28-
client.connect(function(err) {
29-
if(err) return cb(err, null);
306

31-
// Remove the noop error handler after a connection has been established.
32-
client.removeListener('error', Function.prototype);
7+
module.exports = function(Client) {
8+
var pools = {
9+
//dictionary of all key:pool pairs
10+
all: {},
11+
//reference to the client constructor - can override in tests or for require('pg').native
12+
getOrCreate: function(clientConfig) {
13+
clientConfig = clientConfig || {};
14+
var name = JSON.stringify(clientConfig);
15+
var pool = pools.all[name];
16+
if(pool) {
17+
return pool;
18+
}
19+
pool = genericPool.Pool({
20+
name: name,
21+
max: clientConfig.poolSize || defaults.poolSize,
22+
idleTimeoutMillis: clientConfig.poolIdleTimeout || defaults.poolIdleTimeout,
23+
reapIntervalMillis: clientConfig.reapIntervalMillis || defaults.reapIntervalMillis,
24+
log: clientConfig.poolLog || defaults.poolLog,
25+
create: function(cb) {
26+
var client = new Client(clientConfig);
27+
// Ignore errors on pooled clients until they are connected.
28+
client.on('error', Function.prototype);
29+
client.connect(function(err) {
30+
if(err) return cb(err, null);
3331

34-
//handle connected client background errors by emitting event
35-
//via the pg object and then removing errored client from the pool
36-
client.on('error', function(e) {
37-
pool.emit('error', e, client);
32+
// Remove the noop error handler after a connection has been established.
33+
client.removeListener('error', Function.prototype);
3834

39-
// If the client is already being destroyed, the error
40-
// occurred during stream ending. Do not attempt to destroy
41-
// the client again.
42-
if (!client._destroying) {
43-
pool.destroy(client);
44-
}
45-
});
35+
//handle connected client background errors by emitting event
36+
//via the pg object and then removing errored client from the pool
37+
client.on('error', function(e) {
38+
pool.emit('error', e, client);
4639

47-
// Remove connection from pool on disconnect
48-
client.on('end', function(e) {
49-
// Do not enter infinite loop between pool.destroy
50-
// and client 'end' event...
51-
if ( ! client._destroying ) {
40+
// If the client is already being destroyed, the error
41+
// occurred during stream ending. Do not attempt to destroy
42+
// the client again.
43+
if (!client._destroying) {
44+
pool.destroy(client);
45+
}
46+
});
47+
48+
// Remove connection from pool on disconnect
49+
client.on('end', function(e) {
50+
// Do not enter infinite loop between pool.destroy
51+
// and client 'end' event...
52+
if ( ! client._destroying ) {
53+
pool.destroy(client);
54+
}
55+
});
56+
client.poolCount = 0;
57+
return cb(null, client);
58+
});
59+
},
60+
destroy: function(client) {
61+
client._destroying = true;
62+
client.poolCount = undefined;
63+
client.end();
64+
}
65+
});
66+
pools.all[name] = pool;
67+
//mixin EventEmitter to pool
68+
EventEmitter.call(pool);
69+
for(var key in EventEmitter.prototype) {
70+
if(EventEmitter.prototype.hasOwnProperty(key)) {
71+
pool[key] = EventEmitter.prototype[key];
72+
}
73+
}
74+
//monkey-patch with connect method
75+
pool.connect = function(cb) {
76+
var domain = process.domain;
77+
pool.acquire(function(err, client) {
78+
if(domain) {
79+
cb = domain.bind(cb);
80+
}
81+
if(err) return cb(err, null, function() {/*NOOP*/});
82+
client.poolCount++;
83+
cb(null, client, function(err) {
84+
if(err) {
5285
pool.destroy(client);
86+
} else {
87+
pool.release(client);
5388
}
5489
});
55-
client.poolCount = 0;
56-
return cb(null, client);
5790
});
58-
},
59-
destroy: function(client) {
60-
client._destroying = true;
61-
client.poolCount = undefined;
62-
client.end();
63-
}
64-
});
65-
pools.all[name] = pool;
66-
//mixin EventEmitter to pool
67-
EventEmitter.call(pool);
68-
for(var key in EventEmitter.prototype) {
69-
if(EventEmitter.prototype.hasOwnProperty(key)) {
70-
pool[key] = EventEmitter.prototype[key];
71-
}
91+
};
92+
return pool;
7293
}
73-
//monkey-patch with connect method
74-
pool.connect = function(cb) {
75-
var domain = process.domain;
76-
pool.acquire(function(err, client) {
77-
if(domain) {
78-
cb = domain.bind(cb);
79-
}
80-
if(err) return cb(err, null, function() {/*NOOP*/});
81-
client.poolCount++;
82-
cb(null, client, function(err) {
83-
if(err) {
84-
pool.destroy(client);
85-
} else {
86-
pool.release(client);
87-
}
88-
});
89-
});
90-
};
91-
return pool;
92-
}
93-
};
94+
};
9495

95-
module.exports = pools;
96+
return pools;
97+
};
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
var helper = require(__dirname + '/../test-helper');
2+
3+
//native bindings are only installed for native tests
4+
if(!helper.args.native) {
5+
return;
6+
}
7+
8+
var assert = require('assert')
9+
var pg = require('../../../lib')
10+
var native = require('../../../lib').native
11+
12+
var JsClient = require('../../../lib/client')
13+
var NativeClient = require('../../../lib/native')
14+
15+
assert(pg.Client === JsClient);
16+
assert(native.Client === NativeClient);
17+
18+
pg.connect(function(err, client, done) {
19+
assert(client instanceof JsClient);
20+
client.end();
21+
22+
native.connect(function(err, client, done) {
23+
assert(client instanceof NativeClient);
24+
client.end();
25+
});
26+
});
27+

test/unit/pool/basic-tests.js

+7-8
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ var util = require('util');
22
var EventEmitter = require('events').EventEmitter;
33

44
var libDir = __dirname + '/../../../lib';
5+
var poolsFactory = require(libDir + '/pool')
56
var defaults = require(libDir + '/defaults');
6-
var pools = require(libDir + '/pool');
77
var poolId = 0;
88

99
require(__dirname + '/../../test-helper');
@@ -21,6 +21,7 @@ FakeClient.prototype.connect = function(cb) {
2121
FakeClient.prototype.end = function() {
2222
this.endCalled = true;
2323
}
24+
var pools = poolsFactory(FakeClient);
2425

2526
//Hangs the event loop until 'end' is called on client
2627
var HangingClient = function(config) {
@@ -41,8 +42,6 @@ HangingClient.prototype.end = function() {
4142
clearInterval(this.intervalId);
4243
}
4344

44-
pools.Client = FakeClient;
45-
4645
test('no pools exist', function() {
4746
assert.empty(Object.keys(pools.all));
4847
});
@@ -115,7 +114,7 @@ test('on client error, client is removed from pool', function() {
115114
});
116115

117116
test('pool with connection error on connection', function() {
118-
pools.Client = function() {
117+
var errorPools = poolsFactory(function() {
119118
return {
120119
connect: function(cb) {
121120
process.nextTick(function() {
@@ -124,9 +123,10 @@ test('pool with connection error on connection', function() {
124123
},
125124
on: Function.prototype
126125
};
127-
};
126+
})
127+
128128
test('two parameters', function() {
129-
var p = pools.getOrCreate(poolId++);
129+
var p = errorPools.getOrCreate(poolId++);
130130
p.connect(assert.calls(function(err, client) {
131131
assert.ok(err);
132132
assert.equal(client, null);
@@ -136,7 +136,7 @@ test('pool with connection error on connection', function() {
136136
}));
137137
});
138138
test('three parameters', function() {
139-
var p = pools.getOrCreate(poolId++);
139+
var p = errorPools.getOrCreate(poolId++);
140140
var tid = setTimeout(function() {
141141
assert.fail('Did not call connect callback');
142142
}, 100);
@@ -155,7 +155,6 @@ test('pool with connection error on connection', function() {
155155

156156
test('returnning an error to done()', function() {
157157
var p = pools.getOrCreate(poolId++);
158-
pools.Client = FakeClient;
159158
p.connect(function(err, client, done) {
160159
assert.equal(err, null);
161160
assert(client);

test/unit/pool/timeout-tests.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ var EventEmitter = require('events').EventEmitter;
33

44
var libDir = __dirname + '/../../../lib';
55
var defaults = require(libDir + '/defaults');
6-
var pools = require(libDir + '/pool');
6+
var poolsFactory = require(libDir + '/pool');
77
var poolId = 0;
88

99
require(__dirname + '/../../test-helper');
@@ -25,8 +25,9 @@ FakeClient.prototype.end = function() {
2525
defaults.poolIdleTimeout = 10;
2626
defaults.reapIntervalMillis = 10;
2727

28+
var pools = poolsFactory(FakeClient)
29+
2830
test('client times out from idle', function() {
29-
pools.Client = FakeClient;
3031
var p = pools.getOrCreate(poolId++);
3132
p.connect(function(err, client, done) {
3233
done();

0 commit comments

Comments
 (0)