Skip to content
16 changes: 16 additions & 0 deletions src/mongo_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,8 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements
override readonly mongoLogger: MongoLogger | undefined;
/** @internal */
private connectionLock?: Promise<this>;
/** @internal */
private closeLock?: Promise<void>;

/**
* The consolidate, parsed, transformed and merged options.
Expand Down Expand Up @@ -638,6 +640,20 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements
* @param force - Force close, emitting no events
*/
async close(force = false): Promise<void> {
if (this.closeLock) {
return await this.closeLock;
}

try {
this.closeLock = this._close(force);
await this.closeLock;
} finally {
// release
this.closeLock = undefined;
}
}

async _close(force = false): Promise<void> {
// There's no way to set hasBeenClosed back to false
Object.defineProperty(this.s, 'hasBeenClosed', {
value: true,
Expand Down
16 changes: 16 additions & 0 deletions test/integration/node-specific/mongo_client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,22 @@ describe('class MongoClient', function () {
expect(client.s.sessionPool.sessions).to.have.lengthOf(1);
});
});

context('concurrent calls', () => {
context('when two concurrent calls to close() occur', () => {
it('no error is thrown', async function () {
expect(() => Promise.all([client.close(), client.close()])).to.not.throw;
});
});

context('when more than two concurrent calls to close() occur', () => {
it('no error is thrown', async function () {
expect(() =>
Promise.all([client.close(), client.close(), client.close(), client.close()])
).to.not.throw;
});
});
});
});

context('when connecting', function () {
Expand Down
18 changes: 18 additions & 0 deletions test/unit/mongo_client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1223,4 +1223,22 @@ describe('MongoClient', function () {
});
});
});

describe('closeLock', function () {
let client;

beforeEach(async function () {
client = new MongoClient('mongodb://blah');
});

it('when client.close is pending, client.closeLock is set', () => {
client.close();
expect(client.closeLock).to.be.instanceOf(Promise);
});

it('when client.close is not pending, client.closeLock is not set', async () => {
await client.close();
expect(client.closeLock).to.be.undefined;
});
});
});
Loading