Skip to content

Commit e296c99

Browse files
nbbeekenljhaywar
authored andcommitted
fix: move session support check to operation layer (#2750)
Session support check is now performed after server selection this ensures the monitor was able to update the topology description NODE-3100
1 parent a67c847 commit e296c99

File tree

5 files changed

+46
-47
lines changed

5 files changed

+46
-47
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

+21-22
Original file line numberDiff line numberDiff line change
@@ -61,33 +61,32 @@ export function executeOperation<
6161
throw new TypeError('This method requires a valid operation instance');
6262
}
6363

64-
if (topology.shouldCheckForSessionSupport()) {
65-
return maybePromise(callback, cb => {
66-
topology.selectServer(ReadPreference.primaryPreferred, err => {
67-
if (err) {
68-
cb(err);
69-
return;
70-
}
64+
return maybePromise(callback, cb => {
65+
if (topology.shouldCheckForSessionSupport()) {
66+
return topology.selectServer(ReadPreference.primaryPreferred, err => {
67+
if (err) return cb(err);
7168

7269
executeOperation<T, TResult>(topology, operation, cb);
7370
});
74-
});
75-
}
71+
}
7672

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

90-
return maybePromise(callback, cb => {
9190
try {
9291
executeWithServerSelection(topology, session, operation, (err, result) => {
9392
if (session && session.owner && session.owner === owner) {
@@ -113,7 +112,7 @@ function supportsRetryableReads(server: Server) {
113112
function executeWithServerSelection(
114113
topology: Topology,
115114
session: ClientSession,
116-
operation: any,
115+
operation: AbstractOperation,
117116
callback: Callback
118117
) {
119118
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;

src/sessions.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -571,16 +571,14 @@ function endTransaction(session: ClientSession, commandName: string, callback: C
571571
});
572572
}
573573

574-
executeOperation(
574+
return executeOperation(
575575
session.topology,
576576
new RunAdminCommandOperation(undefined, command, {
577577
session,
578578
readPreference: ReadPreference.primary
579579
}),
580580
(_err, _reply) => commandHandler(_err as MongoError, _reply)
581581
);
582-
583-
return;
584582
}
585583

586584
commandHandler(err as MongoError, reply);

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)