From 36b91782242570e46ab6c947684e57a10290b819 Mon Sep 17 00:00:00 2001 From: Sergey Zelenov Date: Fri, 20 Feb 2026 10:48:57 +0100 Subject: [PATCH 1/3] fix(NODE-7455): clear timeout on retry in server selection --- src/sdam/topology.ts | 6 ++ .../server_selection_timeout_cleanup.test.ts | 83 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 test/integration/server-selection/server_selection_timeout_cleanup.test.ts diff --git a/src/sdam/topology.ts b/src/sdam/topology.ts index 2a158525fa0..c44989d66f2 100644 --- a/src/sdam/topology.ts +++ b/src/sdam/topology.ts @@ -596,6 +596,9 @@ export class Topology extends TypedEventEmitter { ); } if (options.timeoutContext?.clearServerSelectionTimeout) timeout?.clear(); + // if (!options.timeoutContext || options.timeoutContext.clearServerSelectionTimeout) { + // timeout?.clear(); + // } return transaction.server; } @@ -667,6 +670,9 @@ export class Topology extends TypedEventEmitter { } finally { abortListener?.[kDispose](); if (options.timeoutContext?.clearServerSelectionTimeout) timeout?.clear(); + // if (!options.timeoutContext || options.timeoutContext.clearServerSelectionTimeout) { + // timeout?.clear(); + // } } } /** diff --git a/test/integration/server-selection/server_selection_timeout_cleanup.test.ts b/test/integration/server-selection/server_selection_timeout_cleanup.test.ts new file mode 100644 index 00000000000..ef98df2cb2a --- /dev/null +++ b/test/integration/server-selection/server_selection_timeout_cleanup.test.ts @@ -0,0 +1,83 @@ +import { expect } from 'chai'; +import * as process from 'process'; +import { setTimeout } from 'timers/promises'; + +import { type Collection, type MongoClient } from '../../mongodb'; + +describe.only('server selection timeout cleanup', function () { + let client: MongoClient; + let collection: Collection; + let utilClients: MongoClient[]; + + describe( + 'timeout cleanup on retries', + { requires: { topology: 'sharded', mongodb: '>=4.4' } }, + function () { + beforeEach(async function () { + client = this.configuration.newClient({ serverSelectionTimeoutMS: 500, retryWrites: true }); + await client.connect(); + + collection = client.db('server_selection').collection('timeout_cleanup'); + + // we need to configure failpoint for every mongos as we don't know where the session will be pinned to + const seeds = client.topology.s.seedlist.map(address => address.toString()); + for (const seed of seeds) { + const c = this.configuration.newClient(`mongodb://${seed}`, { + directConnection: true + }); + await c.connect(); + await c.db('admin').command({ + configureFailPoint: 'failCommand', + mode: { times: 1 }, + data: { + failCommands: ['insert'], + errorCode: 6, + errorsLabels: ['RetryableWriteError'], + closeConnection: false + } + }); + utilClients.push(c); + } + }); + + afterEach(async function () { + for (const c of utilClients) { + await c.db('admin').command({ + configureFailPoint: 'failCommand', + mode: 'off', + data: { + failCommands: ['insert'], + errorCode: 6, + errorsLabels: ['RetryableWriteError'], + closeConnection: false + } + }); + await c.close(); + } + await client.close(); + }); + + it('does not leak timeout when retrying inside a sharded transaction', async function () { + const unhandlerRejections = []; + const handler = reason => unhandlerRejections.push(reason); + process.on('unhandledRejection', handler); + + try { + const session = client.startSession(); + try { + session.startTransaction(); + await collection.find({}, { session }).toArray(); + await collection.insertOne({ foo: 'bar' }, { session }); + await session.commitTransaction(); + } finally { + await session.endSession(); + } + await setTimeout(1000); + expect(unhandlerRejections.length).to.have.lengthOf(0); + } finally { + process.removeListener('unhandledRejection', handler); + } + }); + } + ); +}); From 5a0f79e9c0d511d8af3cd0a7b6bb2104eed4035a Mon Sep 17 00:00:00 2001 From: Sergey Zelenov Date: Fri, 20 Feb 2026 10:56:28 +0100 Subject: [PATCH 2/3] fix typo --- .../server_selection_timeout_cleanup.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/integration/server-selection/server_selection_timeout_cleanup.test.ts b/test/integration/server-selection/server_selection_timeout_cleanup.test.ts index ef98df2cb2a..99ff89a59e3 100644 --- a/test/integration/server-selection/server_selection_timeout_cleanup.test.ts +++ b/test/integration/server-selection/server_selection_timeout_cleanup.test.ts @@ -14,11 +14,15 @@ describe.only('server selection timeout cleanup', function () { { requires: { topology: 'sharded', mongodb: '>=4.4' } }, function () { beforeEach(async function () { - client = this.configuration.newClient({ serverSelectionTimeoutMS: 500, retryWrites: true }); + client = this.configuration.newClient( + this.configuration.url({ useMultipleMongoses: true }), + { serverSelectionTimeoutMS: 500, retryWrites: true } + ); await client.connect(); collection = client.db('server_selection').collection('timeout_cleanup'); + utilClients = []; // we need to configure failpoint for every mongos as we don't know where the session will be pinned to const seeds = client.topology.s.seedlist.map(address => address.toString()); for (const seed of seeds) { From f47b4c4a44e2d6938ddcb743832c8270802edd54 Mon Sep 17 00:00:00 2001 From: Sergey Zelenov Date: Fri, 20 Feb 2026 11:09:51 +0100 Subject: [PATCH 3/3] fix typo in errorLabels --- .../server-selection/server_selection_timeout_cleanup.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/server-selection/server_selection_timeout_cleanup.test.ts b/test/integration/server-selection/server_selection_timeout_cleanup.test.ts index 99ff89a59e3..7e929a5f08f 100644 --- a/test/integration/server-selection/server_selection_timeout_cleanup.test.ts +++ b/test/integration/server-selection/server_selection_timeout_cleanup.test.ts @@ -36,7 +36,7 @@ describe.only('server selection timeout cleanup', function () { data: { failCommands: ['insert'], errorCode: 6, - errorsLabels: ['RetryableWriteError'], + errorLabels: ['RetryableWriteError'], closeConnection: false } }); @@ -52,7 +52,7 @@ describe.only('server selection timeout cleanup', function () { data: { failCommands: ['insert'], errorCode: 6, - errorsLabels: ['RetryableWriteError'], + errorLabels: ['RetryableWriteError'], closeConnection: false } });