Skip to content

Commit fbb4bb4

Browse files
committed
fix: move session support check to operation layer
Session support check is now performed after server selection this ensures the monitor was able to update the topology description NODE-3100
1 parent ee1a4d3 commit fbb4bb4

File tree

4 files changed

+42
-39
lines changed

4 files changed

+42
-39
lines changed

src/mongo_client.ts

-4
Original file line numberDiff line numberDiff line change
@@ -464,10 +464,6 @@ export class MongoClient extends EventEmitter {
464464
throw new MongoError('Must connect to a server before calling this method');
465465
}
466466

467-
if (!this.topology.hasSessionSupport()) {
468-
throw new MongoError('Current topology does not support sessions');
469-
}
470-
471467
return this.topology.startSession(options, this.s.options);
472468
}
473469

src/operations/execute_operation.ts

+18-17
Original file line numberDiff line numberDiff line change
@@ -64,30 +64,31 @@ export function executeOperation<
6464
if (topology.shouldCheckForSessionSupport()) {
6565
return maybePromise(callback, cb => {
6666
topology.selectServer(ReadPreference.primaryPreferred, err => {
67-
if (err) {
68-
cb(err);
69-
return;
70-
}
67+
if (err) return cb(err);
7168

7269
executeOperation<T, TResult>(topology, operation, cb);
7370
});
7471
});
7572
}
7673

77-
// The driver sessions spec mandates that we implicitly create sessions for operations
78-
// that are not explicitly provided with a session.
79-
let session = operation.session;
80-
let owner: symbol;
81-
if (topology.hasSessionSupport()) {
82-
if (session == null) {
83-
owner = Symbol();
84-
session = topology.startSession({ owner, explicit: false });
85-
} else if (operation.session.hasEnded) {
86-
throw new MongoError('Use of expired sessions is not permitted');
74+
return maybePromise(callback, cb => {
75+
// The driver sessions spec mandates that we implicitly create sessions for operations
76+
// that are not explicitly provided with a session.
77+
let session: ClientSession | undefined = operation.session;
78+
let owner: symbol | undefined;
79+
if (topology.hasSessionSupport()) {
80+
if (session == null) {
81+
owner = Symbol();
82+
session = topology.startSession({ owner, explicit: false });
83+
} else if (session.hasEnded) {
84+
return cb(new MongoError('Use of expired sessions is not permitted'));
85+
}
86+
} else if (session) {
87+
// If the user passed an explicit session and we are still, after server selection,
88+
// trying to run against a topology that doesn't support sessions we error out.
89+
return cb(new MongoError('Current topology does not support sessions'));
8790
}
88-
}
8991

90-
return maybePromise(callback, cb => {
9192
try {
9293
executeWithServerSelection(topology, session, operation, (err, result) => {
9394
if (session && session.owner && session.owner === owner) {
@@ -113,7 +114,7 @@ function supportsRetryableReads(server: Server) {
113114
function executeWithServerSelection(
114115
topology: Topology,
115116
session: ClientSession,
116-
operation: any,
117+
operation: AbstractOperation,
117118
callback: Callback
118119
) {
119120
const readPreference = operation.readPreference || ReadPreference.primary;

src/operations/operation.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const kSession = Symbol('session');
3838
* a specific aspect.
3939
* @internal
4040
*/
41-
export abstract class AbstractOperation<T> {
41+
export abstract class AbstractOperation<TResult = any> {
4242
ns!: MongoDBNamespace;
4343
cmd!: Document;
4444
readPreference: ReadPreference;
@@ -48,6 +48,9 @@ export abstract class AbstractOperation<T> {
4848
// BSON serialization options
4949
bsonOptions?: BSONSerializeOptions;
5050

51+
// TODO: Each operation defines its own options, there should be better typing here
52+
options: Document;
53+
5154
[kSession]: ClientSession;
5255

5356
constructor(options: OperationOptions = {}) {
@@ -61,9 +64,11 @@ export abstract class AbstractOperation<T> {
6164
if (options.session) {
6265
this[kSession] = options.session;
6366
}
67+
68+
this.options = options;
6469
}
6570

66-
abstract execute(server: Server, session: ClientSession, callback: Callback<T>): void;
71+
abstract execute(server: Server, session: ClientSession, callback: Callback<TResult>): void;
6772

6873
hasAspect(aspect: symbol): boolean {
6974
const ctor = this.constructor as OperationConstructor;

test/unit/sessions/client.test.js

+17-16
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ describe('Sessions - client/unit', function () {
1414
});
1515
});
1616

17-
it('should throw an exception if sessions are not supported', {
17+
it('should not throw a synchronous exception if sessions are not supported', {
1818
metadata: { requires: { topology: 'single' } },
19-
test: function (done) {
19+
test() {
2020
test.server.setMessageHandler(request => {
2121
var doc = request.document;
2222
if (doc.ismaster) {
@@ -27,13 +27,11 @@ describe('Sessions - client/unit', function () {
2727
});
2828

2929
const client = this.configuration.newClient(`mongodb://${test.server.uri()}/test`);
30-
client.connect(function (err, client) {
31-
expect(err).to.not.exist;
32-
expect(() => {
33-
client.startSession();
34-
}).to.throw(/Current topology does not support sessions/);
35-
36-
client.close(done);
30+
return client.connect().then(() => {
31+
expect(() => client.startSession()).to.not.throw(
32+
'Current topology does not support sessions'
33+
);
34+
return client.close();
3735
});
3836
}
3937
});
@@ -93,15 +91,18 @@ describe('Sessions - client/unit', function () {
9391
return replicaSetMock.uri();
9492
})
9593
.then(uri => {
96-
const client = this.configuration.newClient(uri);
97-
return client.connect();
94+
testClient = this.configuration.newClient(uri);
95+
return testClient.connect();
9896
})
9997
.then(client => {
100-
testClient = client;
101-
expect(client.topology.s.description.logicalSessionTimeoutMinutes).to.not.exist;
102-
expect(() => {
103-
client.startSession();
104-
}).to.throw(/Current topology does not support sessions/);
98+
const session = client.startSession();
99+
return client.db().collection('t').insertOne({ a: 1 }, { session });
100+
})
101+
.then(() => {
102+
expect.fail('Expected an error to be thrown about not supporting sessions');
103+
})
104+
.catch(error => {
105+
expect(error.message).to.equal('Current topology does not support sessions');
105106
})
106107
.finally(() => (testClient ? testClient.close() : null));
107108
}

0 commit comments

Comments
 (0)