Skip to content

Commit 76a4a8d

Browse files
don't send endSessions to cryptd + prose test
1 parent 25de3df commit 76a4a8d

File tree

2 files changed

+103
-41
lines changed

2 files changed

+103
-41
lines changed

src/mongo_client.ts

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -795,38 +795,42 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements
795795
return;
796796
}
797797

798-
// If we would attempt to select a server and get nothing back we short circuit
799-
// to avoid the server selection timeout.
800-
const selector = readPreferenceServerSelector(ReadPreference.primaryPreferred);
801798
const topologyDescription = this.topology.description;
802-
const serverDescriptions = Array.from(topologyDescription.servers.values());
803-
const servers = selector(topologyDescription, serverDescriptions);
804-
if (servers.length !== 0) {
805-
const endSessions = Array.from(this.s.sessionPool.sessions, ({ id }) => id);
806-
if (endSessions.length !== 0) {
807-
try {
808-
class EndSessionsOperation extends AbstractOperation<void> {
809-
override ns = MongoDBNamespace.fromString('admin.$cmd');
810-
override SERVER_COMMAND_RESPONSE_TYPE = MongoDBResponse;
811-
override buildCommand(_connection: Connection, _session?: ClientSession): Document {
812-
return {
813-
endSessions
814-
};
815-
}
816-
override buildOptions(timeoutContext: TimeoutContext): ServerCommandOptions {
817-
return {
818-
timeoutContext,
819-
readPreference: ReadPreference.primaryPreferred,
820-
noResponse: true
821-
};
822-
}
823-
override get commandName(): string {
824-
return 'endSessions';
799+
const areSessionsSupported = topologyDescription.logicalSessionTimeoutMinutes != null;
800+
801+
if (areSessionsSupported) {
802+
// If we would attempt to select a server and get nothing back we short circuit
803+
// to avoid the server selection timeout.
804+
const selector = readPreferenceServerSelector(ReadPreference.primaryPreferred);
805+
const serverDescriptions = Array.from(topologyDescription.servers.values());
806+
const servers = selector(topologyDescription, serverDescriptions);
807+
if (servers.length !== 0) {
808+
const endSessions = Array.from(this.s.sessionPool.sessions, ({ id }) => id);
809+
if (endSessions.length !== 0) {
810+
try {
811+
class EndSessionsOperation extends AbstractOperation<void> {
812+
override ns = MongoDBNamespace.fromString('admin.$cmd');
813+
override SERVER_COMMAND_RESPONSE_TYPE = MongoDBResponse;
814+
override buildCommand(_connection: Connection, _session?: ClientSession): Document {
815+
return {
816+
endSessions
817+
};
818+
}
819+
override buildOptions(timeoutContext: TimeoutContext): ServerCommandOptions {
820+
return {
821+
timeoutContext,
822+
readPreference: ReadPreference.primaryPreferred,
823+
noResponse: true
824+
};
825+
}
826+
override get commandName(): string {
827+
return 'endSessions';
828+
}
825829
}
830+
await executeOperation(this, new EndSessionsOperation());
831+
} catch (error) {
832+
squashError(error);
826833
}
827-
await executeOperation(this, new EndSessionsOperation());
828-
} catch (error) {
829-
squashError(error);
830834
}
831835
}
832836
}

test/integration/node-specific/client_close.test.ts

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
1+
import { type ChildProcess, spawn } from 'node:child_process';
12
import * as events from 'node:events';
3+
import { tmpdir } from 'node:os';
4+
import { join } from 'node:path';
25

36
import { expect } from 'chai';
47

58
import { getCSFLEKMSProviders } from '../../csfle-kms-providers';
6-
import { type Collection, type FindCursor, type MongoClient } from '../../mongodb';
9+
import {
10+
type Collection,
11+
type CommandStartedEvent,
12+
type FindCursor,
13+
type MongoClient,
14+
ObjectId
15+
} from '../../mongodb';
16+
import { filterForCommands } from '../shared';
717
import { runScriptAndGetProcessInfo } from './resource_tracking_script_builder';
818

919
describe('MongoClient.close() Integration', () => {
@@ -490,20 +500,68 @@ describe('MongoClient.close() Integration', () => {
490500
});
491501

492502
describe('when MongoClient.close is called', function () {
493-
it('sends an endSessions command', async function () {
494-
await client.db('a').collection('a').insertOne({ a: 1 });
495-
await client.db('a').collection('a').insertOne({ a: 1 });
496-
await client.db('a').collection('a').insertOne({ a: 1 });
497-
const endSessionsStarted = events.once(client, 'commandStarted');
498-
const willEndSessions = events.once(client, 'commandSucceeded');
503+
describe('when sessions are supported', function () {
504+
it('sends an endSessions command', async function () {
505+
await client.db('a').collection('a').insertOne({ a: 1 });
506+
await client.db('a').collection('a').insertOne({ a: 1 });
507+
await client.db('a').collection('a').insertOne({ a: 1 });
508+
const endSessionsStarted = events.once(client, 'commandStarted');
509+
const willEndSessions = events.once(client, 'commandSucceeded');
499510

500-
await client.close();
511+
await client.close();
512+
513+
const [startedEv] = await endSessionsStarted;
514+
expect(startedEv).to.have.nested.property('command.endSessions').that.has.lengthOf(1);
515+
516+
const [commandEv] = await willEndSessions;
517+
expect(commandEv).to.have.property('commandName', 'endSessions');
518+
});
519+
});
501520

502-
const [startedEv] = await endSessionsStarted;
503-
expect(startedEv).to.have.nested.property('command.endSessions').that.has.lengthOf(1);
521+
describe('when sessions are not supported', function () {
522+
const mongocryptdTestPort = '27022';
523+
let childProcess: ChildProcess;
524+
let client: MongoClient;
525+
const commands: Array<CommandStartedEvent> = [];
526+
527+
beforeEach(async function () {
528+
const pidFile = join(tmpdir(), new ObjectId().toHexString());
529+
childProcess = spawn(
530+
'mongocryptd',
531+
['--port', mongocryptdTestPort, '--ipv6', '--pidfilepath', pidFile],
532+
{
533+
stdio: 'ignore',
534+
detached: true
535+
}
536+
);
537+
538+
childProcess.on('error', err => {
539+
console.warn('Sessions prose mongocryptd error:', err);
540+
});
541+
});
504542

505-
const [commandEv] = await willEndSessions;
506-
expect(commandEv).to.have.property('commandName', 'endSessions');
543+
beforeEach('configure cryptd client and prepopulate session pool', async function () {
544+
client = this.configuration.newClient(`mongodb://localhost:${mongocryptdTestPort}`, {
545+
monitorCommands: true
546+
});
547+
548+
client.on('commandStarted', filterForCommands('endSessions', commands));
549+
550+
// run an operation to instantiate an implicit session (which should be omitted) from the
551+
// actual command but still instantiated by the client. See session prose test 18.
552+
await client.db().command({ hello: true });
553+
expect(client.s.sessionPool.sessions).to.have.length.greaterThan(0);
554+
});
555+
556+
afterEach(() => {
557+
childProcess.kill();
558+
});
559+
560+
it('does not execute endSessions', async function () {
561+
await client.close();
562+
563+
expect(commands).to.deep.equal([]);
564+
});
507565
});
508566
});
509567
});

0 commit comments

Comments
 (0)