Skip to content

Commit a81245b

Browse files
daprahamianmbroadst
authored andcommitted
feat(sessions): adding implicit cursor session support
Adds support for implicit cursor sessions. Cursors will now lazily create sessions for their operations if no session is supplied, and will take care of cleaning up the created session when the cursor is exhausted. Fixes NODE-1326
1 parent da51ac5 commit a81245b

File tree

4 files changed

+47
-15
lines changed

4 files changed

+47
-15
lines changed

lib/command_cursor.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ var CommandCursor = function(bson, ns, cmd, options, topology, topologyOptions)
8989
// Topology Options
9090
topologyOptions: topologyOptions,
9191
// Promise library
92-
promiseLibrary: promiseLibrary
92+
promiseLibrary: promiseLibrary,
93+
// Optional ClientSession
94+
session: options.session
9395
};
9496
};
9597

@@ -145,7 +147,9 @@ var methodsToInherit = [
145147
'isDead',
146148
'explain',
147149
'isNotified',
148-
'isKilled'
150+
'isKilled',
151+
'_endSession',
152+
'_initImplicitSession'
149153
];
150154

151155
// Only inherit the types we need

lib/cursor.js

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -194,14 +194,33 @@ var Cursor = function(bson, ns, cmd, options, topology, topologyOptions) {
194194
inherits(Cursor, Readable);
195195

196196
// Map core cursor _next method so we can apply mapping
197-
CoreCursor.prototype._next = CoreCursor.prototype.next;
197+
Cursor.prototype._next = function() {
198+
if (this._initImplicitSession) {
199+
this._initImplicitSession();
200+
}
201+
return CoreCursor.prototype.next.apply(this, arguments);
202+
};
198203

199204
for (var name in CoreCursor.prototype) {
200205
Cursor.prototype[name] = CoreCursor.prototype[name];
201206
}
202207

203208
var define = (Cursor.define = new Define('Cursor', Cursor, true));
204209

210+
Cursor.prototype._initImplicitSession = function() {
211+
if (!this.s.session && this.s.topology.hasSessionSupport()) {
212+
this.s.session = this.s.topology.startSession({ owner: this });
213+
this.cursorState.session = this.s.session;
214+
}
215+
};
216+
217+
Cursor.prototype._endSession = function() {
218+
const didCloseCursor = CoreCursor.prototype._endSession.apply(this, arguments);
219+
if (didCloseCursor) {
220+
this.s.session = undefined;
221+
}
222+
};
223+
205224
/**
206225
* Check if there is any document still available in the cursor
207226
* @method
@@ -929,7 +948,11 @@ var toArray = function(self, callback) {
929948
// Fetch all the documents
930949
var fetchDocs = function() {
931950
self._next(function(err, doc) {
932-
if (err) return handleCallback(callback, err);
951+
if (err) {
952+
return self._endSession
953+
? self._endSession(() => handleCallback(callback, err))
954+
: handleCallback(callback, err);
955+
}
933956
if (doc == null) {
934957
return self.close({ skipKillCursors: true }, () => handleCallback(callback, null, items));
935958
}
@@ -985,17 +1008,21 @@ Cursor.prototype.count = function(applySkipLimit, opts, callback) {
9851008
if (typeof opts === 'function') (callback = opts), (opts = {});
9861009
opts = opts || {};
9871010

988-
return executeOperation(this.s.topology, count, [this, applySkipLimit, opts, callback], {
989-
skipSessions: true
990-
});
991-
};
992-
993-
var count = function(self, applySkipLimit, opts, callback) {
9941011
if (typeof applySkipLimit === 'function') {
9951012
callback = applySkipLimit;
9961013
applySkipLimit = true;
9971014
}
9981015

1016+
if (this.s.session) {
1017+
opts = Object.assign({}, opts, { session: this.s.session });
1018+
}
1019+
1020+
return executeOperation(this.s.topology, count, [this, applySkipLimit, opts, callback], {
1021+
skipSessions: !!this.s.session
1022+
});
1023+
};
1024+
1025+
var count = function(self, applySkipLimit, opts, callback) {
9991026
if (applySkipLimit) {
10001027
if (typeof self.cursorSkip() === 'number') opts.skip = self.cursorSkip();
10011028
if (typeof self.cursorLimit() === 'number') opts.limit = self.cursorLimit();
@@ -1080,7 +1107,7 @@ Cursor.prototype.close = function(options, callback) {
10801107
};
10811108

10821109
if (this.s.session) {
1083-
return this.s.session.endSession(() => completeClose());
1110+
return this._endSession(() => completeClose());
10841111
}
10851112

10861113
return completeClose();

lib/mongo_client.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,7 @@ define.staticMethod('connect', { callback: true, promise: true });
495495
* @return {ClientSession} the newly established session
496496
*/
497497
MongoClient.prototype.startSession = function(options) {
498-
options = options || {};
498+
options = Object.assign({ explicit: true }, options);
499499
if (!this.topology) {
500500
throw new MongoError('Must connect to a server before calling this method');
501501
}

lib/utils.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -378,11 +378,12 @@ const executeOperation = (topology, operation, args, options) => {
378378

379379
// The driver sessions spec mandates that we implicitly create sessions for operations
380380
// that are not explicitly provided with a session.
381-
let session, opOptions;
381+
let session, opOptions, owner;
382382
if (!options.skipSessions && topology.hasSessionSupport()) {
383383
opOptions = args[args.length - 2];
384384
if (opOptions == null || opOptions.session == null) {
385-
session = topology.startSession();
385+
owner = {};
386+
session = topology.startSession({ owner });
386387
const optionsIndex = args.length - 2;
387388
args[optionsIndex] = Object.assign({}, args[optionsIndex], { session: session });
388389
} else if (opOptions.session && opOptions.session.hasEnded) {
@@ -392,7 +393,7 @@ const executeOperation = (topology, operation, args, options) => {
392393

393394
const makeExecuteCallback = (resolve, reject) =>
394395
function executeCallback(err, result) {
395-
if (session && !options.returnsCursor) {
396+
if (session && session.owner === owner && !options.returnsCursor) {
396397
session.endSession(() => {
397398
delete opOptions.session;
398399
if (err) return reject(err);

0 commit comments

Comments
 (0)