Skip to content

fix(NODE-5106): prevent multiple mongo client connect()s from leaking topology #3596

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Mar 28, 2023
73 changes: 43 additions & 30 deletions src/mongo_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,8 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> {
topology?: Topology;
/** @internal */
readonly mongoLogger: MongoLogger;
/** @internal */
private connectionLock?: Promise<this>;

/**
* The consolidate, parsed, transformed and merged options.
Expand Down Expand Up @@ -409,46 +411,57 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> {
return this;
}

const options = this[kOptions];
if (this.connectionLock) {
return this.connectionLock;
}

this.connectionLock = (async () => {
const options = this[kOptions];

if (typeof options.srvHost === 'string') {
const hosts = await resolveSRVRecord(options);
if (typeof options.srvHost === 'string') {
const hosts = await resolveSRVRecord(options);

for (const [index, host] of hosts.entries()) {
options.hosts[index] = host;
for (const [index, host] of hosts.entries()) {
options.hosts[index] = host;
}
}
}

const topology = new Topology(options.hosts, options);
// Events can be emitted before initialization is complete so we have to
// save the reference to the topology on the client ASAP if the event handlers need to access it
this.topology = topology;
topology.client = this;
const topology = new Topology(options.hosts, options);
// Events can be emitted before initialization is complete so we have to
// save the reference to the topology on the client ASAP if the event handlers need to access it
this.topology = topology;
topology.client = this;

topology.once(Topology.OPEN, () => this.emit('open', this));
topology.once(Topology.OPEN, () => this.emit('open', this));

for (const event of MONGO_CLIENT_EVENTS) {
topology.on(event, (...args: any[]) => this.emit(event, ...(args as any)));
}
for (const event of MONGO_CLIENT_EVENTS) {
topology.on(event, (...args: any[]) => this.emit(event, ...(args as any)));
}

const topologyConnect = async () => {
try {
await promisify(callback => topology.connect(options, callback))();
} catch (error) {
topology.close({ force: true });
throw error;
const topologyConnect = async () => {
try {
await promisify(callback => topology.connect(options, callback))();
} catch (error) {
topology.close({ force: true });
throw error;
}
};

if (this.autoEncrypter) {
const initAutoEncrypter = promisify(callback => this.autoEncrypter?.init(callback));
await initAutoEncrypter();
await topologyConnect();
await options.encrypter.connectInternalClient();
} else {
await topologyConnect();
}
};

if (this.autoEncrypter) {
const initAutoEncrypter = promisify(callback => this.autoEncrypter?.init(callback));
await initAutoEncrypter();
await topologyConnect();
await options.encrypter.connectInternalClient();
} else {
await topologyConnect();
}
return this;
})();

await this.connectionLock;
// release
this.connectionLock = undefined;
return this;
}

Expand Down