Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions test/integration/node-specific/client_close.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { TestConfiguration } from '../../tools/runner/config';
import { runScriptAndReturnResourceInfo } from './resource_tracking_script_builder';

describe.only('client.close() Integration', () => {
let config: TestConfiguration;
beforeEach(function () {
config = this.configuration;
});

describe('MongoClient', () => {
describe('when client is being instantiated and reads a long docker file', () => {
// our docker env detection uses fs.access which will not be aborted until after it runs
// fs.access does not support abort signals
it('the file read is not interrupted by client.close', () => {
});
});
describe('when client is connecting and reads a TLS long file', () => {
it('the file read is interrupted by client.close', () => {
});
});
});

describe('MongoClientAuthProviders', () => {
describe('when MongoClientAuthProviders is instantiated', () => {
it('the token cache is cleaned up by client.close', () => {
});
});
});

describe('Topology', () => {
describe('after a Topology is explicitly created', () => {
it('timers are cleaned up by client.close()', () => {

});
});
describe('after a Topology is created through client.connect()', () => {
it('timers are cleaned up by client.close()', () => {

});
});
});

describe('SRVPoller', () => {
describe('after SRVPoller is explicitly created', () => {
it('timers are cleaned up by client.close()', () => {

});
});

// SRVPoller is implicitly created after an SRV string's topology transitions to sharded
describe('after SRVPoller is implicitly created', () => {
it('timers are cleaned up by client.close()', () => {

});
});
});

describe('ClientSession', () => {
describe('after a clientSession is created', () => {
it('the server-side ServerSession and transaction are cleaned up by client.close()', () => {

});
});
});

describe('StateMachine', () => {
describe('when FLE is enabled and the client has made a KMS request', () => {
it('no sockets remain after client.close', () => {

});
it('no server-side connection threads remain after client.close', () => {

});
describe('when the TLS file read hangs', () => {
it('the file read is interrupted by client.close', () => {

});
});
});
});

describe('ConnectionPool', () => {
describe('after new connection pool is created', () => {
it('minPoolSize timer is cleaned up by client.close()', () => {

});
});
});

describe('MonitorInterval', () => {
describe('after a new monitor is made', () => {
it('monitor interval timer is cleaned up by client.close()', () => {

});
});

describe('after a heartbeat fails', () => {
it('monitor interval timer is cleaned up by client.close()', () => {

});
});
});

describe('RTTPinger', () => {
describe('after helloReply has a topologyVersion defined fails', () => {
it('rtt pinger timer is cleaned up by client.close()', () => {

});
});
});

describe('Connection', () => {
describe('when connection monitoring is turned on', () => {
it('no sockets remain after client.close', () => {

});
it('no server-side connection threads remain after client.close', () => {

});
});

describe('when rtt monitoring is turned on', () => {
it('no sockets remain after client.close', () => {

});
it('no server-side connection threads remain after client.close', () => {

});
});

describe('after a connection is checked out', () => {
it('no sockets remain after client.close', () => {

});
it('no server-side connection threads remain after client.close', () => {

});
});

describe('after a minPoolSize has been set on the ConnectionPool', () => {
it('no sockets remain after client.close', () => {

});
it('no server-side connection threads remain after client.close', () => {

});
});
});

describe('Cursor', () => {
describe('after cursors are created', () => {
it('all active server-side cursors are closed by client.close()', () => {

});
});
});
});
4 changes: 2 additions & 2 deletions test/integration/node-specific/resource_clean_up.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as v8 from 'node:v8';
import { expect } from 'chai';

import { sleep } from '../../tools/utils';
import { runScript } from './resource_tracking_script_builder';
import { runScriptAndReturnHeapInfo } from './resource_tracking_script_builder';

/**
* This 5MB range is selected arbitrarily and should likely be raised if failures are seen intermittently.
Expand Down Expand Up @@ -38,7 +38,7 @@ describe('Driver Resources', () => {
return;
}
try {
const res = await runScript(
const res = await runScriptAndReturnHeapInfo(
'no_resource_leak_connect_close',
this.configuration,
async function run({ MongoClient, uri }) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,26 @@ export type ResourceTestFunction = (options: {
iteration: number;
}) => Promise<void>;

const RESOURCE_SCRIPT_PATH = path.resolve(__dirname, '../../tools/fixtures/resource_script.in.js');
const HEAP_RESOURCE_SCRIPT_PATH = path.resolve(__dirname, '../../tools/fixtures/resource_script.in.js');
const REPORT_RESOURCE_SCRIPT_PATH = path.resolve(__dirname, '../../tools/fixtures/close_resource_script.in.js');
const DRIVER_SRC_PATH = JSON.stringify(path.resolve(__dirname, '../../../lib'));

export async function testScriptFactory(
name: string,
uri: string,
iterations: number,
func: ResourceTestFunction
resourceScriptPath: string,
func: ResourceTestFunction,
iterations?: number,
) {
let resourceScript = await readFile(RESOURCE_SCRIPT_PATH, { encoding: 'utf8' });
let resourceScript = await readFile(resourceScriptPath, { encoding: 'utf8' });

resourceScript = resourceScript.replace('DRIVER_SOURCE_PATH', DRIVER_SRC_PATH);
resourceScript = resourceScript.replace('FUNCTION_STRING', `(${func.toString()})`);
resourceScript = resourceScript.replace('NAME_STRING', JSON.stringify(name));
resourceScript = resourceScript.replace('URI_STRING', JSON.stringify(uri));
resourceScript = resourceScript.replace('ITERATIONS_STRING', `${iterations}`);
if (resourceScriptPath === HEAP_RESOURCE_SCRIPT_PATH) {
resourceScript = resourceScript.replace('ITERATIONS_STRING', `${iterations}`);
}

return resourceScript;
}
Expand All @@ -57,7 +61,7 @@ export async function testScriptFactory(
* @param options - settings for the script
* @throws Error - if the process exits with failure
*/
export async function runScript(
export async function runScriptAndReturnHeapInfo(
name: string,
config: TestConfiguration,
func: ResourceTestFunction,
Expand All @@ -66,7 +70,7 @@ export async function runScript(
const scriptName = `${name}.cjs`;
const heapsnapshotFile = `${name}.heapsnapshot.json`;

const scriptContent = await testScriptFactory(name, config.url(), iterations, func);
const scriptContent = await testScriptFactory(name, config.url(), HEAP_RESOURCE_SCRIPT_PATH, func, iterations);
await writeFile(scriptName, scriptContent, { encoding: 'utf8' });

const processDiedController = new AbortController();
Expand Down Expand Up @@ -106,3 +110,34 @@ export async function runScript(
heap
};
}

export async function runScriptAndReturnResourceInfo(
name: string,
config: TestConfiguration,
func: ResourceTestFunction
) {

const scriptName = `scripts/${name}.cjs`;
const scriptContent = await testScriptFactory(name, config.url(), REPORT_RESOURCE_SCRIPT_PATH, func);
await writeFile(scriptName, scriptContent, { encoding: 'utf8' });

const processDiedController = new AbortController();
const script = fork(name);

// Interrupt our awaiting of messages if the process crashed
script.once('close', exitCode => {
if (exitCode !== 0) {
processDiedController.abort(new Error(`process exited with: ${exitCode}`));
}
});

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

// make sure the process ended
const [exitCode] = await willClose;
expect(exitCode, 'process should have exited with zero').to.equal(0);

const messages = on(script, 'message', { signal: processDiedController.signal });
const report = await messages.next();
return report;
}
34 changes: 34 additions & 0 deletions test/tools/fixtures/close_resource_script.in.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use strict';

/* eslint-disable no-undef */

const driverPath = DRIVER_SOURCE_PATH;
const func = FUNCTION_STRING;
const name = NAME_STRING;
const uri = URI_STRING;
const iterations = ITERATIONS_STRING;

const { MongoClient } = require(driverPath);
const process = require('node:process');
const v8 = require('node:v8');
const util = require('node:util');
const timers = require('node:timers');

const run = func;

async function main() {
process.on('beforeExit', (code) => {
process.send({beforeExit: true});
});
await run({ MongoClient, uri, iteration });
const report = process.report.getReport();
process.send({ report });
}

main()
.then(() => {
process.exit(0);
})
.catch(() => {
process.exit(1);
});
Loading