Skip to content

Commit c24cf28

Browse files
committed
fix: reauth must finish before check in
1 parent 7f14566 commit c24cf28

File tree

2 files changed

+76
-8
lines changed

2 files changed

+76
-8
lines changed

src/sdam/server.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,8 @@ export class Server extends TypedEventEmitter<ServerEvents> {
333333
}
334334
}
335335

336+
let reauthPromise: Promise<void> | null = null;
337+
336338
try {
337339
try {
338340
const res = await conn.command(ns, cmd, options, responseType);
@@ -346,7 +348,14 @@ export class Server extends TypedEventEmitter<ServerEvents> {
346348
operationError instanceof MongoError &&
347349
operationError.code === MONGODB_ERROR_CODES.Reauthenticate
348350
) {
349-
await abortable(this.pool.reauthenticate(conn), options);
351+
reauthPromise = this.pool.reauthenticate(conn).catch(error => {
352+
reauthPromise = null;
353+
throw error;
354+
});
355+
356+
await abortable(reauthPromise, options);
357+
reauthPromise = null; // only reachable if reauth succeeds
358+
350359
try {
351360
const res = await conn.command(ns, cmd, options, responseType);
352361
throwIfWriteConcernError(res);
@@ -360,7 +369,14 @@ export class Server extends TypedEventEmitter<ServerEvents> {
360369
} finally {
361370
this.decrementOperationCount();
362371
if (session?.pinnedConnection !== conn) {
363-
this.pool.checkIn(conn);
372+
if (reauthPromise != null) {
373+
// The reauth promise only exists if it hasn't thrown.
374+
void reauthPromise.finally(() => {
375+
this.pool.checkIn(conn);
376+
});
377+
} else {
378+
this.pool.checkIn(conn);
379+
}
364380
}
365381
}
366382
}

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

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
Code,
1212
type Collection,
1313
Connection,
14+
ConnectionPool,
1415
type Db,
1516
FindCursor,
1617
ListCollectionsCursor,
@@ -721,6 +722,14 @@ describe('AbortSignal support', () => {
721722
let controller: AbortController;
722723
let signal: AbortSignal;
723724

725+
const msOutOfPool = async () => {
726+
await events.once(client, 'connectionCheckedOut');
727+
const start = performance.now();
728+
await events.once(client, 'connectionCheckedIn');
729+
const end = performance.now();
730+
return end - start;
731+
};
732+
724733
class ReAuthenticationError extends MongoServerError {
725734
override code = 391; // reauth code.
726735
}
@@ -741,7 +750,7 @@ describe('AbortSignal support', () => {
741750
...args
742751
) {
743752
if (args[1].find != null) {
744-
sinon.restore();
753+
commandStub.restore();
745754
controller.abort();
746755
throw new ReAuthenticationError({});
747756
}
@@ -750,15 +759,58 @@ describe('AbortSignal support', () => {
750759
});
751760

752761
afterEach(async function () {
762+
sinon.restore();
753763
logs.length = 0;
754764
await client?.close();
755765
});
756766

757-
it('escapes reauth without interrupting it', { requires: { auth: 'enabled' } }, async () => {
758-
const checkIn = events.once(client, 'connectionCheckedIn');
759-
const toArray = cursor.toArray().catch(error => error);
760-
expect(await toArray).to.be.instanceOf(DOMException);
761-
await checkIn; // checks back in despite the abort
767+
describe('if reauth succeeds', () => {
768+
beforeEach(() => {
769+
sinon.stub(ConnectionPool.prototype, 'reauthenticate').callsFake(async function () {
770+
return sleep(1000);
771+
});
772+
});
773+
774+
it(
775+
'escapes reauth without interrupting it and checks in the connection after reauth completes',
776+
{ requires: { auth: 'enabled' } },
777+
async () => {
778+
const checkIn = msOutOfPool();
779+
780+
const start = performance.now();
781+
const toArray = await cursor.toArray().catch(error => error);
782+
const end = performance.now();
783+
expect(end - start).to.be.lessThan(260);
784+
785+
expect(toArray).to.be.instanceOf(DOMException);
786+
expect(await checkIn).to.be.greaterThan(1000); // checks back in despite the abort
787+
}
788+
);
789+
});
790+
791+
describe('if reauth throws', () => {
792+
beforeEach(() => {
793+
sinon.stub(ConnectionPool.prototype, 'reauthenticate').callsFake(async function () {
794+
await sleep(1000);
795+
throw new Error();
796+
});
797+
});
798+
799+
it(
800+
'escapes reauth without interrupting it and checks in the connection after reauth completes',
801+
{ requires: { auth: 'enabled' } },
802+
async () => {
803+
const checkIn = msOutOfPool();
804+
805+
const start = performance.now();
806+
const toArray = await cursor.toArray().catch(error => error);
807+
const end = performance.now();
808+
expect(end - start).to.be.lessThan(260);
809+
810+
expect(toArray).to.be.instanceOf(DOMException);
811+
expect(await checkIn).to.be.greaterThan(1000); // checks back in despite the abort
812+
}
813+
);
762814
});
763815
});
764816

0 commit comments

Comments
 (0)