Skip to content

Commit ba66af3

Browse files
Merge branch 'main' into NODE-6626/server-side
2 parents 71b4b99 + 70d476a commit ba66af3

File tree

3 files changed

+45
-81
lines changed

3 files changed

+45
-81
lines changed

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

Lines changed: 30 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -121,22 +121,16 @@ describe('MongoClient.close() Integration', () => {
121121
'monitor interval timer is cleaned up by client.close()',
122122
metadata,
123123
async function () {
124-
const run = async function ({
125-
MongoClient,
126-
uri,
127-
expect,
128-
getTimerCount,
129-
promiseWithResolvers
130-
}) {
124+
const run = async function ({ MongoClient, uri, expect, getTimerCount, once }) {
131125
const heartbeatFrequencyMS = 2000;
132126
const client = new MongoClient(uri, { heartbeatFrequencyMS });
133-
const { promise, resolve } = promiseWithResolvers();
134-
client.once('serverHeartbeatSucceeded', () => resolve());
127+
const willBeHeartbeatSucceeded = once(client, 'serverHeartbeatSucceeded');
135128
await client.connect();
136-
await promise;
129+
await willBeHeartbeatSucceeded;
137130

138131
function monitorTimersExist(servers) {
139132
for (const [, server] of servers) {
133+
// the current expected behavior is that timerId is set to undefined once it expires or is interrupted
140134
if (server?.monitor.monitorId.timerId === undefined) {
141135
return false;
142136
}
@@ -160,19 +154,12 @@ describe('MongoClient.close() Integration', () => {
160154
'the new monitor interval timer is cleaned up by client.close()',
161155
metadata,
162156
async () => {
163-
const run = async function ({
164-
MongoClient,
165-
expect,
166-
getTimerCount,
167-
promiseWithResolvers
168-
}) {
157+
const run = async function ({ MongoClient, expect, getTimerCount, once }) {
169158
const heartbeatFrequencyMS = 2000;
170159
const client = new MongoClient('mongodb://fakeUri', { heartbeatFrequencyMS });
171-
const { promise, resolve } = promiseWithResolvers();
172-
client.once('serverHeartbeatFailed', () => resolve());
160+
const willBeHeartbeatFailed = once(client, 'serverHeartbeatFailed');
173161
client.connect();
174-
await promise;
175-
162+
await willBeHeartbeatFailed;
176163
function getMonitorTimer(servers) {
177164
for (const [, server] of servers) {
178165
return server?.monitor.monitorId.timerId;
@@ -181,6 +168,7 @@ describe('MongoClient.close() Integration', () => {
181168
const servers = client.topology.s.servers;
182169
expect(getMonitorTimer(servers)).to.exist;
183170
await client.close();
171+
// the current expected behavior is that timerId is set to undefined once it expires or is interrupted
184172
expect(getMonitorTimer(servers)).to.not.exist;
185173

186174
expect(getTimerCount()).to.equal(0);
@@ -226,22 +214,14 @@ describe('MongoClient.close() Integration', () => {
226214
'the rtt pinger timer is cleaned up by client.close()',
227215
metadata,
228216
async function () {
229-
const run = async function ({
230-
MongoClient,
231-
uri,
232-
expect,
233-
getTimerCount,
234-
promiseWithResolvers
235-
}) {
217+
const run = async function ({ MongoClient, uri, expect, getTimerCount, once }) {
236218
const heartbeatFrequencyMS = 2000;
237219
const client = new MongoClient(uri, {
238220
serverMonitoringMode: 'stream',
239221
heartbeatFrequencyMS
240222
});
241223
await client.connect();
242-
const { promise, resolve } = promiseWithResolvers();
243-
client.once('serverHeartbeatSucceeded', () => resolve());
244-
await promise;
224+
await once(client, 'serverHeartbeatSucceeded');
245225

246226
function getRttTimer(servers) {
247227
for (const [, server] of servers) {
@@ -267,13 +247,7 @@ describe('MongoClient.close() Integration', () => {
267247
describe('Node.js resource: Socket', () => {
268248
describe('when rtt monitoring is turned on', () => {
269249
it.skip('no sockets remain after client.close()', metadata, async () => {
270-
const run = async ({
271-
MongoClient,
272-
uri,
273-
expect,
274-
getSockets,
275-
promiseWithResolvers
276-
}) => {
250+
const run = async ({ MongoClient, uri, expect, getSockets, once }) => {
277251
const heartbeatFrequencyMS = 500;
278252
const client = new MongoClient(uri, {
279253
serverMonitoringMode: 'stream',
@@ -289,12 +263,8 @@ describe('MongoClient.close() Integration', () => {
289263
const servers = client.topology.s.servers;
290264

291265
while (heartbeatOccurredSet.size < servers.size) {
292-
const { promise, resolve } = promiseWithResolvers();
293-
client.once('serverHeartbeatSucceeded', ev => {
294-
heartbeatOccurredSet.add(ev.connectionId);
295-
resolve();
296-
});
297-
await promise;
266+
const ev = await once(client, 'serverHeartbeatSucceeded');
267+
heartbeatOccurredSet.add(ev[0].connectionId);
298268
}
299269

300270
const activeSocketsAfterHeartbeat = () =>
@@ -309,7 +279,6 @@ describe('MongoClient.close() Integration', () => {
309279

310280
// close the client
311281
await client.close();
312-
313282
// upon close, assert rttPinger sockets are cleaned up
314283
const activeSocketsAfterClose = activeSocketsAfterHeartbeat();
315284
expect(activeSocketsAfterClose).to.have.lengthOf(0);
@@ -385,36 +354,34 @@ describe('MongoClient.close() Integration', () => {
385354
});
386355

387356
it.skip('the wait queue timer is cleaned up by client.close()', async function () {
388-
const run = async function ({ MongoClient, uri, expect, sleep, getTimerCount }) {
357+
const run = async function ({ MongoClient, uri, expect, getTimerCount, once }) {
389358
const waitQueueTimeoutMS = 1515;
390359

391360
const client = new MongoClient(uri, {
392361
maxPoolSize: 1,
393362
waitQueueTimeoutMS,
394-
appName: 'waitQueueTestClient'
363+
appName: 'waitQueueTestClient',
364+
monitorCommands: true
395365
});
396-
const insertPromise = client
366+
client
397367
.db('db')
398368
.collection('collection')
399369
.insertOne({ x: 1 })
400370
.catch(e => e);
401-
await client.once('commandStarted', v => v);
371+
await once(client, 'connectionCheckedOut');
402372

403-
client
373+
const blockedInsert = client
404374
.db('db')
405375
.collection('collection')
406376
.insertOne({ x: 1 })
407377
.catch(e => e);
408-
await client.once('commandStarted', v => v);
409-
410-
// don't allow entire checkout timer to elapse to ensure close is called mid-timeout
411-
await sleep(waitQueueTimeoutMS / 2);
378+
await once(client, 'connectionCheckOutStarted');
412379

413380
expect(getTimerCount()).to.not.equal(0);
414381
await client.close();
415382
expect(getTimerCount()).to.equal(0);
416383

417-
const err = await insertPromise;
384+
const err = await blockedInsert;
418385
expect(err).to.be.instanceOf(Error);
419386
expect(err.message).to.contain(
420387
'Timed out while checking out a connection from connection pool'
@@ -457,24 +424,19 @@ describe('MongoClient.close() Integration', () => {
457424
describe('SrvPoller', () => {
458425
describe('Node.js resource: Timer', () => {
459426
// requires an srv environment that can transition to sharded
460-
const metadata: MongoDBMetadataUI = {
461-
requires: {
462-
predicate: () =>
463-
process.env.ATLAS_SRV_REPL ? true : 'Skipped: this test requires an SRV environment'
464-
}
465-
};
427+
const metadata: MongoDBMetadataUI = { requires: { topology: 'sharded' } };
466428

467429
describe('after SRVPoller is created', () => {
468430
it.skip('timers are cleaned up by client.close()', metadata, async () => {
469-
const run = async function ({ MongoClient, uri, expect, getTimerCount }) {
470-
const client = new MongoClient(uri);
431+
const run = async function ({ MongoClient, expect, getTimerCount }) {
432+
const SRV_CONNECTION_STRING = `mongodb+srv://test1.test.build.10gen.cc`;
433+
// 27018 localhost.test.build.10gen.cc.
434+
// 27017 localhost.test.build.10gen.cc.
435+
436+
const client = new MongoClient(SRV_CONNECTION_STRING);
471437
await client.connect();
472-
const description = client.topology.s.description;
473-
// simulate transition to sharded
474-
client.topology.emit.skip('topologyDescriptionChanged', description, {
475-
...description,
476-
type: 'Sharded'
477-
});
438+
// the current expected behavior is that _timeout is set to undefined until SRV polling starts
439+
// then _timeout is set to undefined again when SRV polling stops
478440
expect(client.topology.s.srvPoller._timeout).to.exist;
479441
await client.close();
480442
expect(getTimerCount()).to.equal(0);

test/integration/node-specific/resource_tracking_script_builder.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { fork, spawn } from 'node:child_process';
22
import { on, once } from 'node:events';
3+
import { openSync } from 'node:fs';
34
import { readFile, unlink, writeFile } from 'node:fs/promises';
45
import * as path from 'node:path';
56

@@ -31,7 +32,7 @@ export type ProcessResourceTestFunction = (options: {
3132
timers?: typeof timers;
3233
getSocketReport?: () => { host: string; port: string };
3334
getSocketEndpointReport?: () => any;
34-
promiseWithResolvers?: () => any;
35+
once?: () => typeof once;
3536
}) => Promise<void>;
3637

3738
const HEAP_RESOURCE_SCRIPT_PATH = path.resolve(
@@ -176,7 +177,10 @@ export async function runScriptAndGetProcessInfo(
176177
await writeFile(scriptName, scriptContent, { encoding: 'utf8' });
177178
const logFile = name + '.logs.txt';
178179

179-
const script = spawn(process.execPath, [scriptName], { stdio: ['ignore', 'ignore', 'inherit'] });
180+
const stdErrFile = 'err.out';
181+
const script = spawn(process.execPath, [scriptName], {
182+
stdio: ['ignore', 'ignore', openSync(stdErrFile, 'w')]
183+
});
180184

181185
const willClose = once(script, 'close');
182186

@@ -190,9 +194,12 @@ export async function runScriptAndGetProcessInfo(
190194
.map(line => JSON.parse(line))
191195
.reduce((acc, curr) => ({ ...acc, ...curr }), {});
192196

197+
const stdErrSize = await readFile(stdErrFile, { encoding: 'utf8' });
198+
193199
// delete temporary files
194200
await unlink(scriptName);
195201
await unlink(logFile);
202+
await unlink(stdErrFile);
196203

197204
// assertions about exit status
198205
if (exitCode) {
@@ -207,4 +214,7 @@ export async function runScriptAndGetProcessInfo(
207214
expect(messages.beforeExitHappened).to.be.true;
208215
expect(messages.newResources.libuvResources).to.be.empty;
209216
expect(messages.newResources.activeResources).to.be.empty;
217+
218+
// assertion about error output
219+
expect(stdErrSize).to.be.empty;
210220
}

test/tools/fixtures/process_resource_script.in.js

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const fs = require('node:fs');
1515
const { expect } = require('chai');
1616
const timers = require('node:timers');
1717
const { setTimeout } = timers;
18+
const { once } = require('node:events');
1819

1920
let originalReport;
2021
const logFile = scriptName + '.logs.txt';
@@ -71,6 +72,7 @@ function getNewLibuvResourceArray() {
7172
* - `process.getActiveResourcesInfo()` does not contain enough server information we need for our assertions
7273
*
7374
*/
75+
7476
function getNewResources() {
7577
return {
7678
libuvResources: getNewLibuvResourceArray(),
@@ -98,16 +100,6 @@ const getSocketEndpoints = () =>
98100
.libuv.filter(r => r.type === 'tcp')
99101
.map(r => r.remoteEndpoint);
100102

101-
function promiseWithResolvers() {
102-
let resolve;
103-
let reject;
104-
const promise = new Promise((promiseResolve, promiseReject) => {
105-
resolve = promiseResolve;
106-
reject = promiseReject;
107-
});
108-
return { promise, resolve, reject };
109-
}
110-
111103
// A log function for debugging
112104
function log(message) {
113105
// remove outer parentheses for easier parsing
@@ -130,7 +122,7 @@ async function main() {
130122
getTimerCount,
131123
getSockets,
132124
getSocketEndpoints,
133-
promiseWithResolvers
125+
once
134126
});
135127
log({ newResources: getNewResources() });
136128
}

0 commit comments

Comments
 (0)