Skip to content
This repository was archived by the owner on Feb 4, 2022. It is now read-only.

Commit 8d512f1

Browse files
committed
feat(operation-time): track operationTime in relevant sessions
If `causalConsistency` is opted for, then the driver MUST track `operationTime` on incoming server responses. This is one part of full support for causal consistency of reads, the other part will be implemented in the porcelain driver related to read concerns. NODE-1106
1 parent cfe8606 commit 8d512f1

File tree

5 files changed

+102
-7
lines changed

5 files changed

+102
-7
lines changed

lib/connection/pool.js

+16-6
Original file line numberDiff line numberDiff line change
@@ -544,13 +544,23 @@ function messageHandler(self) {
544544
return handleOperationCallback(self, workItem.cb, new MongoError(err));
545545
}
546546

547-
// Look for clusterTime, and update it if necessary
548-
if (message.documents[0] && message.documents[0].hasOwnProperty('$clusterTime')) {
549-
const $clusterTime = message.documents[0].$clusterTime;
550-
self.topology.clusterTime = $clusterTime;
547+
// Look for clusterTime, and operationTime and update them if necessary
548+
if (message.documents[0]) {
549+
if (message.documents[0].$clusterTime) {
550+
const $clusterTime = message.documents[0].$clusterTime;
551+
self.topology.clusterTime = $clusterTime;
552+
553+
if (workItem.session != null) {
554+
resolveClusterTime(workItem.session, $clusterTime);
555+
}
556+
}
551557

552-
if (workItem.session != null) {
553-
resolveClusterTime(workItem.session, $clusterTime);
558+
if (
559+
message.documents[0].operationTime &&
560+
workItem.session &&
561+
workItem.session.supports.causalConsistency
562+
) {
563+
workItem.session.advanceOperationTime(message.documents[0].operationTime);
554564
}
555565
}
556566

lib/sessions.js

+25
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,24 @@ class ClientSession {
1919
throw new Error('ClientSession requires a ServerSessionPool');
2020
}
2121

22+
options = options || {};
2223
this.topology = topology;
2324
this.sessionPool = sessionPool;
2425
this.hasEnded = false;
2526
this.serverSession = sessionPool.acquire();
2627

28+
this.supports = {
29+
causalConsistency: !!options.causalConsistency
30+
};
31+
2732
options = options || {};
2833
if (typeof options.initialClusterTime !== 'undefined') {
2934
this.clusterTime = options.initialClusterTime;
35+
} else {
36+
this.clusterTime = null;
3037
}
38+
39+
this.operationTime = null;
3140
}
3241

3342
/**
@@ -64,6 +73,22 @@ class ClientSession {
6473
// spec indicates that we should ignore all errors for `endSessions`
6574
if (typeof callback === 'function') callback(null, null);
6675
}
76+
77+
/**
78+
* Advances the operationTime for a ClientSession.
79+
*
80+
* @param {object} operationTime the `BSON.Timestamp` of the operation type it is desired to advance to
81+
*/
82+
advanceOperationTime(operationTime) {
83+
if (this.operationTime == null) {
84+
this.operationTime = operationTime;
85+
return;
86+
}
87+
88+
if (operationTime.greaterThan(this.operationTime)) {
89+
this.operationTime = operationTime;
90+
}
91+
}
6792
}
6893

6994
Object.defineProperty(ClientSession.prototype, 'id', {

test/mock/index.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ const DEFAULT_ISMASTER = {
5252
ok: 1
5353
};
5454

55+
const DEFAULT_ISMASTER_36 = Object.assign({}, DEFAULT_ISMASTER, {
56+
maxWireVersion: 6,
57+
logicalSessionTimeoutMinutes: 10
58+
});
59+
5560
/*
5661
* Main module
5762
*/
@@ -67,5 +72,6 @@ module.exports = {
6772
},
6873

6974
cleanup: cleanup,
70-
DEFAULT_ISMASTER: DEFAULT_ISMASTER
75+
DEFAULT_ISMASTER: DEFAULT_ISMASTER,
76+
DEFAULT_ISMASTER_36: DEFAULT_ISMASTER_36
7177
};

test/tests/unit/common.js

+7
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,13 @@ class ReplSetFixture {
9191
}
9292
}
9393

94+
/**
95+
* Creates a cluster time for use in unit testing cluster time gossiping and
96+
* causal consistency.
97+
*
98+
* @param {Number} time the logical time
99+
* @returns a cluster time according to the driver sessions specification
100+
*/
94101
function genClusterTime(time) {
95102
return {
96103
clusterTime: new Timestamp(time),

test/tests/unit/single/sessions_tests.js

+47
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
var Server = require('../../../../lib/topologies/server'),
33
Long = require('bson').Long,
44
ObjectId = require('bson').ObjectId,
5+
Timestamp = require('bson').Timestamp,
56
expect = require('chai').expect,
67
assign = require('../../../../lib/utils').assign,
78
mock = require('../../../mock'),
@@ -578,4 +579,50 @@ describe('Sessions (Single)', function() {
578579
client2.connect();
579580
}
580581
});
582+
583+
it('should track the highest `operationTime` seen, if causal consistency is enabled', {
584+
metadata: { requires: { topology: 'single' } },
585+
test: function(done) {
586+
const client = new Server(test.server.address()),
587+
sessionPool = new ServerSessionPool(client),
588+
session = new ClientSession(client, sessionPool, { causalConsistency: true }),
589+
insertOperationTime1 = Timestamp.fromNumber(Date.now()),
590+
insertOperationTime2 = Timestamp.fromNumber(Date.now() + 10 * 60 * 1000);
591+
592+
let insertCount = 0;
593+
test.server.setMessageHandler(request => {
594+
const doc = request.document;
595+
if (doc.ismaster) {
596+
request.reply(mock.DEFAULT_ISMASTER_36);
597+
} else if (doc.insert) {
598+
request.reply({
599+
ok: 1,
600+
operationTime: insertCount === 0 ? insertOperationTime1 : insertOperationTime2
601+
});
602+
603+
insertCount++;
604+
}
605+
});
606+
607+
client.on('error', done);
608+
client.once('connect', () => {
609+
client.insert('db.test', [{ a: 42 }], { session: session }, err => {
610+
expect(err).to.not.exist;
611+
expect(session.operationTime).to.exist;
612+
expect(session.operationTime).to.eql(insertOperationTime1);
613+
614+
client.insert('db.test', [{ b: 52 }], { session: session }, err => {
615+
expect(err).to.not.exist;
616+
expect(session.operationTime).to.exist;
617+
expect(session.operationTime).to.eql(insertOperationTime2);
618+
619+
client.destroy();
620+
done();
621+
});
622+
});
623+
});
624+
625+
client.connect();
626+
}
627+
});
581628
});

0 commit comments

Comments
 (0)