Skip to content

Commit d59dced

Browse files
committed
feat: add a withConnection helper to the connection pool
This helper allows you to easily manage the "checkedoutness" of a connection. It will automatically check out a connection, pass it to the function you provide, and check it back in for you when your function calls back.
1 parent 7b62d79 commit d59dced

File tree

3 files changed

+115
-2
lines changed

3 files changed

+115
-2
lines changed

lib/cmap/connection.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class Connection extends EventEmitter {
4040
});
4141

4242
stream.on('close', () => {
43-
this[kQueue].forEach(op => op.callback(new MongoError('Connection closed')));
43+
this[kQueue].forEach(op => op.cb(new MongoError('Connection closed')));
4444
this[kQueue].clear();
4545

4646
this.emit('close');

lib/cmap/connection_pool.js

+40-1
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,9 @@ class ConnectionPool extends EventEmitter {
177177
destroyConnection(this, connection, reason);
178178
}
179179

180-
callback(null);
180+
if (typeof callback === 'function') {
181+
callback();
182+
}
181183
}
182184

183185
clear(callback) {
@@ -227,6 +229,34 @@ class ConnectionPool extends EventEmitter {
227229
);
228230
}
229231

232+
/**
233+
* Runs a lambda with an implicitly checked out connection, checking that connection back in when the lambda
234+
* has completed by calling back.
235+
*
236+
* NOTE: please note the required signature of `fn`
237+
*
238+
* @param {ConnectionPool~withConnectionCallback} fn A function which operates on a managed connection
239+
* @param {Function} callback The original callback
240+
* @return {Promise}
241+
*/
242+
withConnection(fn, callback) {
243+
this.checkOut((err, conn) => {
244+
// don't callback with `err` here, we might want to act upon it inside `fn`
245+
246+
fn(err, conn, (fnErr, result) => {
247+
if (fnErr) {
248+
callback(fnErr);
249+
} else {
250+
callback(undefined, result);
251+
}
252+
253+
if (conn) {
254+
this.checkIn(conn);
255+
}
256+
});
257+
});
258+
}
259+
230260
get totalConnectionCount() {
231261
return this[kConnections].length + (this.options.maxPoolSize - this[kPermits]);
232262
}
@@ -302,6 +332,15 @@ function destroyConnection(pool, connection, reason) {
302332
process.nextTick(() => connection.destroy());
303333
}
304334

335+
/**
336+
* A callback provided to `withConnection`
337+
*
338+
* @callback ConnectionPool~withConnectionCallback
339+
* @param {MongoError} error An error instance representing the error during the execution.
340+
* @param {Connection} connection The managed connection which was checked out of the pool.
341+
* @param {Function} callback A function to call back after connection management is complete
342+
*/
343+
305344
module.exports = {
306345
ConnectionPool
307346
};

test/unit/cmap/connection_pool.test.js

+74
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,80 @@ describe('Connection Pool', function() {
4545
mock.createServer().then(s => (server = s));
4646
});
4747

48+
describe('withConnection', function() {
49+
it('should manage a connection for a successful operation', function(done) {
50+
server.setMessageHandler(request => {
51+
const doc = request.document;
52+
if (doc.ismaster) {
53+
request.reply(mock.DEFAULT_ISMASTER_36);
54+
}
55+
});
56+
57+
const pool = new ConnectionPool(Object.assign({ bson: new BSON() }, server.address()));
58+
const callback = (err, result) => {
59+
expect(err).to.not.exist;
60+
expect(result).to.exist;
61+
pool.close(done);
62+
};
63+
64+
pool.withConnection((err, conn, cb) => {
65+
expect(err).to.not.exist;
66+
67+
conn.command('$admin.cmd', { ismaster: 1 }, (cmdErr, ismaster) => {
68+
expect(cmdErr).to.not.exist;
69+
cb(undefined, ismaster);
70+
});
71+
}, callback);
72+
});
73+
74+
it('should allow user interaction with an error', function(done) {
75+
server.setMessageHandler(request => {
76+
const doc = request.document;
77+
if (doc.ismaster) {
78+
request.connection.destroy();
79+
}
80+
});
81+
82+
const pool = new ConnectionPool(
83+
Object.assign({ bson: new BSON(), waitQueueTimeoutMS: 250 }, server.address())
84+
);
85+
86+
const callback = err => {
87+
expect(err).to.exist;
88+
expect(err).to.match(/Timed out/);
89+
pool.close(done);
90+
};
91+
92+
pool.withConnection((err, conn, cb) => {
93+
expect(err).to.exist;
94+
expect(err).to.match(/Timed out/);
95+
cb(err);
96+
}, callback);
97+
});
98+
99+
it('should return an error to the original callback', function(done) {
100+
server.setMessageHandler(request => {
101+
const doc = request.document;
102+
if (doc.ismaster) {
103+
request.reply(mock.DEFAULT_ISMASTER_36);
104+
}
105+
});
106+
107+
const pool = new ConnectionPool(Object.assign({ bson: new BSON() }, server.address()));
108+
const callback = (err, result) => {
109+
expect(err).to.exist;
110+
expect(result).to.not.exist;
111+
expect(err).to.match(/my great error/);
112+
pool.close(done);
113+
};
114+
115+
pool.withConnection((err, conn, cb) => {
116+
expect(err).to.not.exist;
117+
cb(new Error('my great error'));
118+
}, callback);
119+
});
120+
});
121+
48122
describe('spec tests', function() {
49123
const threads = new Map();
50124
const connections = new Map();

0 commit comments

Comments
 (0)