Skip to content

Commit 515f718

Browse files
committed
Merge connection pool cache feature
1 parent db9ef8b commit 515f718

File tree

11 files changed

+461
-90
lines changed

11 files changed

+461
-90
lines changed

doc/api.md

Lines changed: 228 additions & 50 deletions
Large diffs are not rendered by default.

lib/connection.js

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -159,23 +159,19 @@ function rollback(rollbackCb) {
159159
rollbackPromisified = nodbUtil.promisify(rollback);
160160

161161
// This release function is used to override the release method of the Connection
162-
// class, which is defined in the C layer. Currently the main difference is that
163-
// connections obtained from a pool need to invoke the pool's _onConnectionRelease
164-
// method so the pool can dequeue the next connection request.
162+
// class, which is defined in the C layer.
165163
function release(releaseCb) {
166164
var self = this;
167165

168166
nodbUtil.assert(arguments.length === 1, 'NJS-009');
169167
nodbUtil.assert(typeof releaseCb === 'function', 'NJS-006', 1);
170168

171169
self._release(function(err) {
172-
releaseCb(err);
173-
174-
// pool will only exist for connections obtained from a pool.
175-
if (self._pool && self._pool.queueRequests !== false) {
176-
self._pool._onConnectionRelease();
170+
if (!err) {
171+
self.emit('_after_close');
177172
}
178173

174+
releaseCb(err);
179175
});
180176
}
181177

@@ -198,6 +194,8 @@ breakPromisified = nodbUtil.promisify(module.break);
198194
// custom properties and method overrides. References to the original methods are
199195
// maintained so they can be invoked by the overriding method at the right time.
200196
function extend(conn, oracledb, pool) {
197+
nodbUtil.makeEventEmitter(conn);
198+
201199
// Using Object.defineProperties to add properties to the Connection instance with
202200
// special properties, such as enumerable but not writable.
203201
Object.defineProperties(
@@ -206,7 +204,7 @@ function extend(conn, oracledb, pool) {
206204
_oracledb: { // storing a reference to the base instance to avoid circular references with require
207205
value: oracledb
208206
},
209-
_pool: {
207+
_pool: { // storing a reference to the pool, if any, from which the connection was obtained
210208
value: pool
211209
},
212210
_execute: {

lib/oracledb.js

Lines changed: 137 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ var connection = require('./connection.js');
2727
var nodbUtil = require('./util.js');
2828
var createPoolPromisified;
2929
var getConnectionPromisified;
30+
var poolCache = {};
31+
var tempUsedPoolAliases = {};
32+
var defaultPoolAlias = 'default';
3033

3134
try {
3235
oracledbCLib = require('../build/Release/oracledb');
@@ -55,45 +58,163 @@ oracledbCLib.Oracledb.prototype.newLob = function(iLob) {
5558
// things like extend out the pool instance prior to passing it to the caller.
5659
function createPool(poolAttrs, createPoolCb) {
5760
var self = this;
61+
var poolAlias;
5862

63+
// Initial argument count and type checks are done first and throw in the same
64+
// call stack.
5965
nodbUtil.assert(arguments.length === 2, 'NJS-009');
6066
nodbUtil.assert(nodbUtil.isObject(poolAttrs), 'NJS-006', 1);
6167
nodbUtil.assert(typeof createPoolCb === 'function', 'NJS-006', 2);
6268

69+
// Additional validations should pass errors via the callback. Need to ensure
70+
// that errors are raised prior to actually creating the pool via _createPool.
71+
if (poolAttrs.poolAlias !== undefined) {
72+
if (typeof poolAttrs.poolAlias !== 'string' || poolAttrs.poolAlias.length === 0) {
73+
createPoolCb(new Error(nodbUtil.getErrorMessage('NJS-004', 'poolAttrs.poolAlias')));
74+
return;
75+
}
76+
77+
poolAlias = poolAttrs.poolAlias;
78+
} else if (poolAttrs.poolAlias === undefined
79+
&& !poolCache[defaultPoolAlias]
80+
&& !tempUsedPoolAliases[defaultPoolAlias]
81+
) {
82+
poolAlias = defaultPoolAlias;
83+
}
84+
85+
if (poolCache[poolAlias] || tempUsedPoolAliases[poolAlias]) {
86+
createPoolCb(new Error(nodbUtil.getErrorMessage('NJS-046', poolAlias)));
87+
return;
88+
}
89+
90+
// Need to prevent another call in the same stack from succeeding, otherwise
91+
// two pools could be created with the same poolAlias and the second one that
92+
// comes back would overwrite the first in the cache.
93+
if (poolAlias) {
94+
tempUsedPoolAliases[poolAlias] = true;
95+
}
96+
6397
self._createPool(poolAttrs, function(err, poolInst) {
6498
if (err) {
99+
// We need to free this up since the creation of the pool failed.
100+
if (poolAlias) {
101+
delete tempUsedPoolAliases[poolAlias];
102+
}
103+
65104
createPoolCb(err);
105+
66106
return;
67107
}
68108

69-
pool.extend(poolInst, poolAttrs, self);
109+
if (poolAlias) {
110+
poolCache[poolAlias] = poolInst;
111+
112+
// It's now safe to remove this alias from the tempUsedPoolAliases.
113+
delete tempUsedPoolAliases[poolAlias];
114+
}
115+
116+
pool.extend(poolInst, poolAttrs, poolAlias, self);
117+
118+
poolInst.on('_after_close', function() {
119+
var pool = this;
120+
121+
if (pool.poolAlias) {
122+
delete poolCache[pool.poolAlias];
123+
}
124+
});
70125

71126
createPoolCb(null, poolInst);
72127
});
73128
}
74129

75130
createPoolPromisified = nodbUtil.promisify(createPool);
76131

132+
// The getPool function is a synchronous method used to retrieve pools from the
133+
// pool cache.
134+
function getPool(poolAlias) {
135+
var pool;
136+
137+
nodbUtil.assert(arguments.length < 2, 'NJS-009');
138+
139+
if (poolAlias) {
140+
nodbUtil.assert(typeof poolAlias === 'string' || typeof poolAlias === 'number', 'NJS-006', 1);
141+
}
142+
143+
poolAlias = poolAlias || defaultPoolAlias;
144+
145+
pool = poolCache[poolAlias];
146+
147+
if (!pool) {
148+
throw new Error(nodbUtil.getErrorMessage('NJS-047', poolAlias));
149+
}
150+
151+
return pool;
152+
}
153+
77154
// This getConnection function is used the override the getConnection method of the
78155
// Oracledb class, which is defined in the C layer. The override allows us to do
79156
// things like extend out the connection instance prior to passing it to the caller.
80-
function getConnection(connAttrs, createConnectionCb) {
157+
function getConnection(a1, a2) {
81158
var self = this;
159+
var pool;
160+
var poolAlias;
161+
var connAttrs;
162+
var getConnectionCb;
82163

83-
nodbUtil.assert(arguments.length === 2, 'NJS-009');
84-
nodbUtil.assert(nodbUtil.isObject(connAttrs), 'NJS-006', 1);
85-
nodbUtil.assert(typeof createConnectionCb === 'function', 'NJS-006', 2);
164+
nodbUtil.assert(arguments.length < 3, 'NJS-009');
86165

87-
self._getConnection(connAttrs, function(err, connInst) {
88-
if (err) {
89-
createConnectionCb(err);
166+
// Verify the number and types of arguments, then initialize the local poolAlias,
167+
// connAttrs, and getConnectionCb variables based on the arguments.
168+
switch (arguments.length) {
169+
case 1:
170+
nodbUtil.assert(typeof a1 === 'function', 'NJS-006', 1);
171+
172+
poolAlias = defaultPoolAlias;
173+
getConnectionCb = a1;
174+
175+
break;
176+
case 2:
177+
nodbUtil.assert(typeof a1 === 'string' || nodbUtil.isObject(a1), 'NJS-006', 1);
178+
nodbUtil.assert(typeof a2 === 'function', 'NJS-006', 2);
179+
180+
if (typeof a1 === 'string') {
181+
poolAlias = a1;
182+
} else if (nodbUtil.isObject(a1)) {
183+
connAttrs = a1;
184+
185+
if (connAttrs.poolAlias) {
186+
poolAlias = connAttrs.poolAlias;
187+
}
188+
}
189+
190+
getConnectionCb = a2;
191+
192+
break;
193+
}
194+
195+
// Proceed to execution based on values in local variables. Look for the poolAlias
196+
// first and only attempt to use connAttrs if the poolAlias isn't set.
197+
if (poolAlias) {
198+
pool = poolCache[poolAlias];
199+
200+
if (!pool) {
201+
getConnectionCb(new Error(nodbUtil.getErrorMessage('NJS-047', poolAlias)));
90202
return;
91203
}
92204

93-
connection.extend(connInst, self);
205+
pool.getConnection(getConnectionCb);
206+
} else {
207+
self._getConnection(connAttrs, function(err, connInst) {
208+
if (err) {
209+
getConnectionCb(err);
210+
return;
211+
}
212+
213+
connection.extend(connInst, self);
94214

95-
createConnectionCb(null, connInst);
96-
});
215+
getConnectionCb(null, connInst);
216+
});
217+
}
97218
}
98219

99220
getConnectionPromisified = nodbUtil.promisify(getConnection);
@@ -258,6 +379,11 @@ function extend(oracledb) {
258379
enumerable: true,
259380
writable: true
260381
},
382+
getPool: {
383+
value: getPool,
384+
enumerable: true,
385+
writable: true
386+
},
261387
_getConnection: {
262388
value: oracledb.getConnection
263389
},

lib/pool.js

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ function completeConnectionRequest(getConnectionCb) {
5555

5656
connection.extend(connInst, self._oracledb, self);
5757

58+
connInst.on('_after_close', function() {
59+
self._connectionsOut -= 1;
60+
61+
checkRequestQueue.call(self);
62+
});
63+
5864
getConnectionCb(null, connInst);
5965
});
6066
}
@@ -95,16 +101,6 @@ function checkRequestQueue() {
95101
completeConnectionRequest.call(self, payload.getConnectionCb);
96102
}
97103

98-
// onConnectionRelease contains the logic that should be executed when a connection
99-
// that was obtained from a pool is released back to the pool.
100-
function onConnectionRelease() {
101-
var self = this;
102-
103-
self._connectionsOut -= 1;
104-
105-
checkRequestQueue.call(self);
106-
}
107-
108104
// onRequestTimeout is used to prevent requests for connections from sitting in the
109105
// queue for too long. The number of milliseconds can be set via queueTimeout
110106
// property of the poolAttrs used when creating a pool.
@@ -219,6 +215,8 @@ function terminate(terminateCb) {
219215
self._terminate(function(err) {
220216
if (!err) {
221217
self._isValid = false;
218+
219+
self.emit('_after_close', self);
222220
}
223221

224222
terminateCb(err);
@@ -265,6 +263,7 @@ function logStats() {
265263
console.log('...pool connections in use:', self.connectionsInUse);
266264
console.log('...pool connections open:', self.connectionsOpen);
267265
console.log('Related pool attributes:');
266+
console.log('...poolAlias:', self.poolAlias);
268267
console.log('...queueRequests:', self.queueRequests);
269268
console.log('...queueTimeout (milliseconds):', self.queueTimeout);
270269
console.log('...poolMin:', self.poolMin);
@@ -279,7 +278,7 @@ function logStats() {
279278
// The extend method is used to extend Pool instances from the C layer with custom
280279
// properties, methods, and method overrides. References to the original methods are
281280
// maintained so they can be invoked by the overriding method at the right time.
282-
function extend(pool, poolAttrs, oracledb) {
281+
function extend(pool, poolAttrs, poolAlias, oracledb) {
283282
var queueRequests;
284283
var queueTimeout;
285284

@@ -295,6 +294,8 @@ function extend(pool, poolAttrs, oracledb) {
295294
queueTimeout = oracledb.queueTimeout;
296295
}
297296

297+
nodbUtil.makeEventEmitter(pool);
298+
298299
// Using Object.defineProperties to add properties to the Pool instance with special
299300
// properties, such as enumerable but not writable.
300301
Object.defineProperties(
@@ -389,12 +390,18 @@ function extend(pool, poolAttrs, oracledb) {
389390
value: {},
390391
writable: true
391392
},
392-
_onConnectionRelease: {
393-
value: onConnectionRelease
394-
},
395393
_getConnection: {
396394
value: pool.getConnection
397395
},
396+
poolAlias: {
397+
enumerable: true,
398+
get: function() {
399+
return poolAlias;
400+
},
401+
set: function() {
402+
throw new Error(nodbUtil.getErrorMessage('NJS-014', 'poolAlias'));
403+
}
404+
},
398405
getConnection: {
399406
value: getConnectionPromisified,
400407
enumerable: true,

lib/util.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,17 @@
1919

2020
var util = require('util');
2121

22+
var EventEmitter = require('events').EventEmitter;
23+
var eventEmitterKeys = Object.keys(EventEmitter.prototype);
24+
var eventEmitterFuncKeys = eventEmitterKeys.filter(function(key) {
25+
return typeof EventEmitter.prototype[key] === 'function';
26+
});
27+
2228
// errorMessages is a temporary duplication of error messages defined in the C
2329
// layer that will be removed once a function to fetch from the C layer is added.
2430
var errorMessages = {
2531
'NJS-002': 'NJS-002: invalid pool',
32+
'NJS-004': 'NJS-004: invalid value for property %s',
2633
'NJS-005': 'NJS-005: invalid value for parameter %d',
2734
'NJS-006': 'NJS-006: invalid type for parameter %d',
2835
'NJS-009': 'NJS-009: invalid number of parameters',
@@ -31,10 +38,26 @@ var errorMessages = {
3138
'NJS-040': 'NJS-040: connection request timeout',
3239
'NJS-041': 'NJS-041: cannot convert ResultSet to QueryStream after invoking methods',
3340
'NJS-042': 'NJS-042: cannot invoke ResultSet methods after converting to QueryStream',
34-
'NJS-043': "NJS-043: ResultSet already converted to QueryStream",
35-
'NJS-045': "NJS-045: cannot load the oracledb add-on binary"
41+
'NJS-043': 'NJS-043: ResultSet already converted to QueryStream',
42+
'NJS-045': 'NJS-045: cannot load the oracledb add-on binary',
43+
'NJS-046': 'NJS-046: poolAlias "%s" already exists in the connection pool cache',
44+
'NJS-047': 'NJS-047: poolAlias "%s" not found in the connection pool cache'
3645
};
3746

47+
// makeEventEmitter is used to make class instances inherit from the EventEmitter
48+
// class. This is needed because we extend instances from the C layer and thus
49+
// don't have JavaScript constructor functions we can use for more traditional
50+
// inheritance.
51+
function makeEventEmitter(instance){
52+
eventEmitterFuncKeys.forEach(function(key) {
53+
instance[key] = EventEmitter.prototype[key];
54+
});
55+
56+
EventEmitter.call(instance);
57+
}
58+
59+
module.exports.makeEventEmitter = makeEventEmitter;
60+
3861
// getErrorMessage is used to get and format error messages to make throwing errors
3962
// a little more convenient.
4063
function getErrorMessage(errorCode, messageArg1) {

src/njs/src/njsMessages.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ static const char *errMsg[] =
8282
"NJS-043: ResultSet already converted to QueryStream", // errResultSetAlreadyConverted
8383
"NJS-044: named JSON object is not expected in this context", // errNamedJSON
8484
"NJS-045: cannot load the oracledb add-on binary", // errCannotLoadBinary
85+
"NJS-046: pool alias \"%s\" already exists in the connection pool cache", // errPoolWithAliasAlreadyExists
86+
"NJS-047: pool alias \"%s\" not found in connection pool cache", // errPoolWithAliasNotFound
8587
};
8688

8789
string NJSMessages::getErrorMsg ( NJSErrorType err, ... )

src/njs/src/njsMessages.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ typedef enum
8181
errResultSetAlreadyConverted,
8282
errNamedJSON,
8383
errCannotLoadBinary,
84+
errPoolWithAliasAlreadyExists,
85+
errPoolWithAliasNotFound,
8486

8587
// New ones should be added here
8688

0 commit comments

Comments
 (0)