-
Type:
Bug
-
Resolution: Gone away
-
Priority:
Major - P3
-
None
-
Affects Version/s: 3.5.6
-
Component/s: None
-
None
-
None
-
None
-
None
-
None
-
None
-
None
If useUnifiedTopology is set while connected to a replicaset, the behavior of the driver to reject operations when connection between app <-> DB is lost is not always consistent.
After the fix mentioned in NODE-2364, the operations are not stuck anymore and properly rejected by the driver. However, this rejection doesn't respect the settings of serverSelectionTimeoutMS, socketTimeoutMS and connectTimeoutMS. In our case, I have made a test from my laptop connecting to a replica set on Mongo Atlas with the aforementioned parameters all set to 2000ms with the expectation that across connection losses (simulated by turning of Wifi periodically for 30s every 1 minute), the queries connecting should be rejected after ~2000ms. However, with a sampling test of ~50 queries, there were always 2-3 cases where the rejected queries were rejected after much longer than ~2000ms as the test result shown below (the 28th query was rejected after 32073ms):
2 => { status: 'released', elapsed: 263 },
3 => { status: 'released', elapsed: 263 },
4 => { status: 'released', elapsed: 263 },
5 => { status: 'released', elapsed: 264 },
6 => { status: 'released', elapsed: 272 },
7 => { status: 'released', elapsed: 236 },
8 => { status: 'released', elapsed: 246 },
9 => { status: 'released', elapsed: 305 },
10 => { status: 'released', elapsed: 242 },
11 => { status: 'released', elapsed: 237 },
12 => { status: 'released', elapsed: 251 },
13 => { status: 'released', elapsed: 236 },
14 => { status: 'released', elapsed: 229 },
15 => { status: 'released', elapsed: 239 },
16 => { status: 'released', elapsed: 238 },
17 => { status: 'released', elapsed: 222 },
18 => { status: 'released', elapsed: 248 },
19 => { status: 'released', elapsed: 254 },
20 => { status: 'released', elapsed: 233 },
21 => { status: 'released', elapsed: 246 },
22 => { status: 'released', elapsed: 240 },
23 => { status: 'released', elapsed: 229 },
24 => { status: 'released', elapsed: 269 },
25 => { status: 'released', elapsed: 251 },
26 => { status: 'released', elapsed: 260 },
27 => { status: 'released', elapsed: 243 },
28 => { status: 'error', elapsed: 32073 },
29 => { status: 'error', elapsed: 2003 },
30 => { status: 'error', elapsed: 2024 },
31 => { status: 'error', elapsed: 2005 },
32 => { status: 'released', elapsed: 245 },
33 => { status: 'released', elapsed: 243 },
34 => { status: 'released', elapsed: 252 },
35 => { status: 'released', elapsed: 509 },
36 => { status: 'error', elapsed: 3037 },
37 => { status: 'error', elapsed: 2003 },
38 => { status: 'error', elapsed: 2007 },
39 => { status: 'error', elapsed: 2006 },
40 => { status: 'error', elapsed: 2002 },
41 => { status: 'error', elapsed: 2006 },
42 => { status: 'error', elapsed: 2004 },
43 => { status: 'error', elapsed: 2009 },
44 => { status: 'error', elapsed: 2005 },
45 => { status: 'error', elapsed: 2007 },
46 => { status: 'error', elapsed: 2011 }
The test code:
const dbServiceProvider = 'test'; const connString = 'mongodb://srv1:27017,srv2:27017,srv3:27017/test?ssl=true&replicaSet=Cluster0-shard-0&authSource=admin&retryWrites=true&w=majority'; const options = { useNewUrlParser: true, useUnifiedTopology: true, minSize: 1, poolSize: 2, ssl: true, serverSelectionTimeoutMS: 2000, socketTimeoutMS: 2000, connectTimeoutMS: 2000 }; function patchEmitter(emitter) { var oldEmit = emitter.emit; emitter.emit = function () { var emitArgs = arguments; console.log(emitArgs[0]); oldEmit.apply(emitter, arguments); } } async function wait(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } async function connect() { const client = await MongoClient.connect(connString, options); patchEmitter(client); return client.db(dbServiceProvider); } var count = 1; var connected = false; var dbClient; var connectionStatus = new Map(); var connectionClosed = false; async function test() { if (!connected) { console.log("connecting ====>"); dbClient = await connect(); connected = true; } while (true) { await wait(2000); var idx = ++count; var time = new Date().getTime(); try { console.error("Connection status: ====>", connectionStatus); connectionStatus.set(idx, { status: "not_released", since: time }); console.log('data loading', idx); const result = await dbClient.collection("tasks").find({}).limit(1).toArray(); console.log('data loaded', idx); connectionStatus.set(idx, { status: "released", elapsed: new Date().getTime() - time }); } catch (e) { console.error(e, 'error'); console.error("elapsed time", new Date().getTime() - time); connectionStatus.set(idx, { status: "error", elapsed: new Date().getTime() - time }); } } } async function closeConnection() { console.error("turn-off wifi =====> ") execSync("sudo networksetup -setnetworkserviceenabled Wi-Fi off") connectionClosed = true setTimeout(openConnection, 30000); } async function openConnection() { console.error("enable wifi =====> ") execSync("sudo networksetup -setnetworkserviceenabled Wi-Fi on") setTimeout(() => { connectionClosed = false }, 5000) } test(); setInterval(closeConnection, 60000);