Skip to content

Commit

Permalink
feat(sessions): MongoClient will now track sessions and release
Browse files Browse the repository at this point in the history
As initially implemented, sessions must be explicitly ended by the
user.  This is likely to lead to cases where users forget to do
this, and therefore leak sessions.  Instead, MongoClient now tracks
all sessions created on the client, and explicitly cleans them up
upon client close.

NODE-1106
  • Loading branch information
mbroadst committed Nov 22, 2017
1 parent 96052c8 commit 6829f47
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 5 deletions.
19 changes: 17 additions & 2 deletions lib/mongo_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@ function MongoClient(url, options) {
url: url,
options: options || {},
promiseLibrary: null,
dbCache: {}
dbCache: {},
sessions: []
};

// Get the promiseLibrary
Expand Down Expand Up @@ -308,6 +309,14 @@ MongoClient.prototype.close = function(force, callback) {
// Remove listeners after emit
self.removeAllListeners('close');

// If we have sessions, we want to send a single `endSessions` command for them,
// and then individually clean them up. They will be removed from the internal state
// when they emit their `ended` events.
if (this.s.sessions.length) {
this.topology.endSessions(this.s.sessions);
this.s.sessions.forEach(session => session.endSession({ skipCommand: true }));
}

// Callback after next event loop tick
if (typeof callback === 'function')
return process.nextTick(function() {
Expand Down Expand Up @@ -481,7 +490,13 @@ MongoClient.prototype.startSession = function(options) {
throw new MongoError('Current topology does not support sessions');
}

return this.topology.startSession(options);
const session = this.topology.startSession(options);
session.once('ended', () => {
this.s.sessions = this.s.sessions.filter(s => s.equals(session));
});

this.s.sessions.push(session);
return session;
};

var mergeOptions = function(target, source, flatten) {
Expand Down
4 changes: 4 additions & 0 deletions lib/topologies/topology_base.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,10 @@ class TopologyBase extends EventEmitter {
return this.s.coreTopology.getServer(options);
}

endSessions(sessions, callback) {
return this.s.coreTopology.endSessions(sessions, callback);
}

/**
* Unref all sockets
* @method
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"official"
],
"dependencies": {
"mongodb-core": "mongodb-js/mongodb-core#8aaac8bcc7bdc02bf8bf4b03337a0af71ddc513e"
"mongodb-core": "mongodb-js/mongodb-core#6510d7d3d82d84cc0811a853355deaa918b96941"
},
"devDependencies": {
"betterbenchmarks": "^0.1.0",
Expand Down
48 changes: 48 additions & 0 deletions test/functional/sessions_tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use strict';
const expect = require('chai').expect,
mongo = require('../..'),
setupDatabase = require('./shared').setupDatabase;

const ignoredCommands = ['ismaster'];
const test = { commands: { started: [], succeeded: [] } };
describe('Sessions', function() {
before(function() {
return setupDatabase(this.configuration);
});

afterEach(() => test.listener.uninstrument());
beforeEach(function() {
test.commands = { started: [], succeeded: [] };
test.listener = mongo.instrument(err => expect(err).to.be.null);
test.listener.on('started', event => {
if (ignoredCommands.indexOf(event.commandName) === -1) test.commands.started.push(event);
});

test.listener.on('succeeded', event => {
if (ignoredCommands.indexOf(event.commandName) === -1) test.commands.succeeded.push(event);
});

test.client = this.configuration.newClient({ w: 1 }, { poolSize: 1, auto_reconnect: false });
return test.client.connect();
});

it('should send endSessions for multiple sessions', {
metadata: { requires: { topology: ['single'] } },
test: function(done) {
var client = this.configuration.newClient({ w: 1 }, { poolSize: 1, auto_reconnect: false });
client.connect((err, client) => {
let sessions = [client.startSession(), client.startSession()].map(s => s.id);

client.close(err => {
expect(err).to.not.exist;
expect(test.commands.started).to.have.length(1);
expect(test.commands.started[0].commandName).to.equal('endSessions');
expect(test.commands.started[0].command.endSessions).to.include.deep.members(sessions);

expect(client.s.sessions).to.have.length(0);
done();
});
});
}
});
});
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2115,9 +2115,9 @@ mongodb-core@mongodb-js/mongodb-core#3.0.0:
bson "~1.0.4"
require_optional "^1.0.1"

mongodb-core@mongodb-js/mongodb-core#8aaac8bcc7bdc02bf8bf4b03337a0af71ddc513e:
mongodb-core@mongodb-js/mongodb-core#6510d7d3d82d84cc0811a853355deaa918b96941:
version "3.0.0-beta2"
resolved "https://codeload.github.com/mongodb-js/mongodb-core/tar.gz/8aaac8bcc7bdc02bf8bf4b03337a0af71ddc513e"
resolved "https://codeload.github.com/mongodb-js/mongodb-core/tar.gz/6510d7d3d82d84cc0811a853355deaa918b96941"
dependencies:
bson "~1.0.4"
require_optional "^1.0.1"
Expand Down

0 comments on commit 6829f47

Please sign in to comment.