From cbe71f9e26b1fc3d36866932cffa5fdd549d9eb9 Mon Sep 17 00:00:00 2001 From: Nepomuk Crhonek <105591323+Nepomuk5665@users.noreply.github.com> Date: Sat, 24 Jan 2026 00:13:11 +0100 Subject: [PATCH 1/3] fix: iterate all servers in closeCheckedOutConnections() The method had a premature 'return' inside the for loop, which caused it to only close connections on the first server and exit immediately. This fix removes the return statement so all servers have their checked-out connections closed when MongoClient.close() is called. This bug would affect multi-server topologies (replica sets, sharded clusters) where only the first server's connections would be properly closed. --- src/sdam/topology.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sdam/topology.ts b/src/sdam/topology.ts index 8ed4899f9b6..722bdb62384 100644 --- a/src/sdam/topology.ts +++ b/src/sdam/topology.ts @@ -494,7 +494,7 @@ export class Topology extends TypedEventEmitter { closeCheckedOutConnections() { for (const server of this.s.servers.values()) { - return server.closeCheckedOutConnections(); + server.closeCheckedOutConnections(); } } From 95fc7ae2093125b2b7d46a85074177f25fd988a0 Mon Sep 17 00:00:00 2001 From: Nepomuk Crhonek <105591323+Nepomuk5665@users.noreply.github.com> Date: Tue, 27 Jan 2026 09:53:45 +0100 Subject: [PATCH 2/3] test: improve ConnectionCheckedInEvent test to validate multi-server deployments - Remove 'single' topology restriction from metadata to support replicaset/sharded - Use readPreference: 'secondaryPreferred' to exercise connections to secondaries - Switch from insert to find operations to validate reads against secondaries --- .../connection_pool.test.ts | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test/integration/connection-monitoring-and-pooling/connection_pool.test.ts b/test/integration/connection-monitoring-and-pooling/connection_pool.test.ts index d073b5a1125..a738f5f1e9c 100644 --- a/test/integration/connection-monitoring-and-pooling/connection_pool.test.ts +++ b/test/integration/connection-monitoring-and-pooling/connection_pool.test.ts @@ -72,7 +72,7 @@ describe('Connection Pool', function () { }); }); - const metadata: MongoDBMetadataUI = { requires: { mongodb: '>=4.4', topology: 'single' } }; + const metadata: MongoDBMetadataUI = { requires: { mongodb: '>=4.4' } }; describe('ConnectionCheckedInEvent', metadata, function () { let client: MongoClient; @@ -89,13 +89,13 @@ describe('Connection Pool', function () { configureFailPoint: 'failCommand', mode: 'alwaysOn', data: { - failCommands: ['insert'], + failCommands: ['find'], blockConnection: true, blockTimeMS: 500 } }); - client = this.configuration.newClient(); + client = this.configuration.newClient({}, { readPreference: 'secondaryPreferred' }); await client.connect(); await Promise.all(Array.from({ length: 100 }, () => client.db().command({ ping: 1 }))); }); @@ -120,37 +120,37 @@ describe('Connection Pool', function () { .on('connectionCheckedIn', pushToClientEvents) .on('connectionClosed', pushToClientEvents); - const inserts = Promise.allSettled([ - client.db('test').collection('test').insertOne({ a: 1 }), - client.db('test').collection('test').insertOne({ a: 1 }), - client.db('test').collection('test').insertOne({ a: 1 }) + const finds = Promise.allSettled([ + client.db('test').collection('test').findOne({ a: 1 }), + client.db('test').collection('test').findOne({ a: 1 }), + client.db('test').collection('test').findOne({ a: 1 }) ]); - // wait until all pings are pending on the server + // wait until all finds are pending on the server while (allClientEvents.filter(e => e.name === 'connectionCheckedOut').length < 3) { await sleep(1); } - const insertConnectionIds = allClientEvents + const findConnectionIds = allClientEvents .filter(e => e.name === 'connectionCheckedOut') .map(({ address, connectionId }) => `${address} + ${connectionId}`); await client.close(); - const insertCheckInAndCloses = allClientEvents + const findCheckInAndCloses = allClientEvents .filter(e => e.name === 'connectionCheckedIn' || e.name === 'connectionClosed') .filter(({ address, connectionId }) => - insertConnectionIds.includes(`${address} + ${connectionId}`) + findConnectionIds.includes(`${address} + ${connectionId}`) ); - expect(insertCheckInAndCloses).to.have.lengthOf(6); + expect(findCheckInAndCloses).to.have.lengthOf(6); // check that each check-in is followed by a close (not proceeded by one) - expect(insertCheckInAndCloses.map(e => e.name)).to.deep.equal( + expect(findCheckInAndCloses.map(e => e.name)).to.deep.equal( Array.from({ length: 3 }, () => ['connectionCheckedIn', 'connectionClosed']).flat(1) ); - await inserts; + await finds; } ); }); From cbf90e8c5ddd8580f1b1e344baf776033898fda9 Mon Sep 17 00:00:00 2001 From: sarthaksoni25 Date: Sat, 11 Apr 2026 16:03:38 +0530 Subject: [PATCH 3/3] test: fix ConnectionCheckedInEvent and add closeCheckedOutConnections test --- .../connection_pool.test.ts | 2 +- .../node-specific/client_close.test.ts | 79 ++++++++++++++++++- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/test/integration/connection-monitoring-and-pooling/connection_pool.test.ts b/test/integration/connection-monitoring-and-pooling/connection_pool.test.ts index a738f5f1e9c..c1cb3b0f499 100644 --- a/test/integration/connection-monitoring-and-pooling/connection_pool.test.ts +++ b/test/integration/connection-monitoring-and-pooling/connection_pool.test.ts @@ -95,7 +95,7 @@ describe('Connection Pool', function () { } }); - client = this.configuration.newClient({}, { readPreference: 'secondaryPreferred' }); + client = this.configuration.newClient({}, {}); await client.connect(); await Promise.all(Array.from({ length: 100 }, () => client.db().command({ ping: 1 }))); }); diff --git a/test/integration/node-specific/client_close.test.ts b/test/integration/node-specific/client_close.test.ts index 98053aa0afb..c5a20a75a09 100644 --- a/test/integration/node-specific/client_close.test.ts +++ b/test/integration/node-specific/client_close.test.ts @@ -10,7 +10,12 @@ import { type FindCursor, type MongoClient } from '../../mongodb'; -import { configureMongocryptdSpawnHooks } from '../../tools/utils'; +import { + clearFailPoint, + configureFailPoint, + configureMongocryptdSpawnHooks, + sleep +} from '../../tools/utils'; import { filterForCommands } from '../shared'; import { runScriptAndGetProcessInfo } from './resource_tracking_script_builder'; @@ -716,6 +721,78 @@ describe('MongoClient.close() Integration', () => { }); }); + describe('closeCheckedOutConnections', () => { + const metadata: MongoDBMetadataUI = { requires: { mongodb: '>=4.4' } }; + let client: MongoClient; + + beforeEach(async function () { + await configureFailPoint(this.configuration, { + configureFailPoint: 'failCommand', + mode: 'alwaysOn', + data: { + failCommands: ['find'], + blockConnection: true, + blockTimeMS: 500 + } + }); + client = this.configuration.newClient({}, {}); + await client.connect(); + }); + + afterEach(async function () { + await clearFailPoint(this.configuration); + await client?.close(); + }); + + it( + 'emits connectionCheckedIn immediately followed by connectionClosed for each in-flight connection', + metadata, + async function () { + const allEvents: Array<{ name: string; address: string; connectionId: number }> = []; + const push = e => allEvents.push(e); + + client + .on('connectionCheckedOut', push) + .on('connectionCheckedIn', push) + .on('connectionClosed', push); + + const finds = Promise.allSettled([ + client.db('test').collection('test').findOne({ a: 1 }), + client.db('test').collection('test').findOne({ a: 1 }), + client.db('test').collection('test').findOne({ a: 1 }) + ]); + + // wait until all three finds have checked out a connection + while (allEvents.filter(e => e.name === 'connectionCheckedOut').length < 3) { + await sleep(1); + } + + const findConnectionIds = new Set( + allEvents + .filter(e => e.name === 'connectionCheckedOut') + .map(({ address, connectionId }) => `${address}+${connectionId}`) + ); + + await client.close(); + + const findEvents = allEvents + .filter(e => e.name === 'connectionCheckedIn' || e.name === 'connectionClosed') + .filter(({ address, connectionId }) => + findConnectionIds.has(`${address}+${connectionId}`) + ); + + expect(findEvents).to.have.lengthOf(6); + + // spec requires each connectionCheckedIn to be immediately followed by connectionClosed + expect(findEvents.map(e => e.name)).to.deep.equal( + Array.from({ length: 3 }, () => ['connectionCheckedIn', 'connectionClosed']).flat() + ); + + await finds; + } + ); + }); + describe('Server resource: Cursor', () => { describe('after cursors are created', () => { let client: MongoClient;