Skip to content

Commit 36b9178

Browse files
committed
fix(NODE-7455): clear timeout on retry in server selection
1 parent e5a85d0 commit 36b9178

2 files changed

Lines changed: 89 additions & 0 deletions

File tree

src/sdam/topology.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,9 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
596596
);
597597
}
598598
if (options.timeoutContext?.clearServerSelectionTimeout) timeout?.clear();
599+
// if (!options.timeoutContext || options.timeoutContext.clearServerSelectionTimeout) {
600+
// timeout?.clear();
601+
// }
599602
return transaction.server;
600603
}
601604

@@ -667,6 +670,9 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
667670
} finally {
668671
abortListener?.[kDispose]();
669672
if (options.timeoutContext?.clearServerSelectionTimeout) timeout?.clear();
673+
// if (!options.timeoutContext || options.timeoutContext.clearServerSelectionTimeout) {
674+
// timeout?.clear();
675+
// }
670676
}
671677
}
672678
/**
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { expect } from 'chai';
2+
import * as process from 'process';
3+
import { setTimeout } from 'timers/promises';
4+
5+
import { type Collection, type MongoClient } from '../../mongodb';
6+
7+
describe.only('server selection timeout cleanup', function () {
8+
let client: MongoClient;
9+
let collection: Collection;
10+
let utilClients: MongoClient[];
11+
12+
describe(
13+
'timeout cleanup on retries',
14+
{ requires: { topology: 'sharded', mongodb: '>=4.4' } },
15+
function () {
16+
beforeEach(async function () {
17+
client = this.configuration.newClient({ serverSelectionTimeoutMS: 500, retryWrites: true });
18+
await client.connect();
19+
20+
collection = client.db('server_selection').collection('timeout_cleanup');
21+
22+
// we need to configure failpoint for every mongos as we don't know where the session will be pinned to
23+
const seeds = client.topology.s.seedlist.map(address => address.toString());
24+
for (const seed of seeds) {
25+
const c = this.configuration.newClient(`mongodb://${seed}`, {
26+
directConnection: true
27+
});
28+
await c.connect();
29+
await c.db('admin').command({
30+
configureFailPoint: 'failCommand',
31+
mode: { times: 1 },
32+
data: {
33+
failCommands: ['insert'],
34+
errorCode: 6,
35+
errorsLabels: ['RetryableWriteError'],
36+
closeConnection: false
37+
}
38+
});
39+
utilClients.push(c);
40+
}
41+
});
42+
43+
afterEach(async function () {
44+
for (const c of utilClients) {
45+
await c.db('admin').command({
46+
configureFailPoint: 'failCommand',
47+
mode: 'off',
48+
data: {
49+
failCommands: ['insert'],
50+
errorCode: 6,
51+
errorsLabels: ['RetryableWriteError'],
52+
closeConnection: false
53+
}
54+
});
55+
await c.close();
56+
}
57+
await client.close();
58+
});
59+
60+
it('does not leak timeout when retrying inside a sharded transaction', async function () {
61+
const unhandlerRejections = [];
62+
const handler = reason => unhandlerRejections.push(reason);
63+
process.on('unhandledRejection', handler);
64+
65+
try {
66+
const session = client.startSession();
67+
try {
68+
session.startTransaction();
69+
await collection.find({}, { session }).toArray();
70+
await collection.insertOne({ foo: 'bar' }, { session });
71+
await session.commitTransaction();
72+
} finally {
73+
await session.endSession();
74+
}
75+
await setTimeout(1000);
76+
expect(unhandlerRejections.length).to.have.lengthOf(0);
77+
} finally {
78+
process.removeListener('unhandledRejection', handler);
79+
}
80+
});
81+
}
82+
);
83+
});

0 commit comments

Comments
 (0)