Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(NODE-4334): check for leaks earlier #3216

Closed
wants to merge 8 commits into from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .evergreen/run-data-lake-tests.sh
Expand Up @@ -4,5 +4,7 @@ set -o errexit # Exit the script with error if any of the commands fail

source "${PROJECT_DIRECTORY}/.evergreen/init-nvm.sh"

export ATLAS_DATA_LAKE="true"

echo "$MONGODB_URI"
npm run check:adl
1 change: 1 addition & 0 deletions .mocharc.json
Expand Up @@ -3,6 +3,7 @@
"require": [
"source-map-support/register",
"ts-node/register",
"test/tools/runner/hooks/resource_check.fixture.ts",
"test/tools/runner/chai-addons.js"
],
"extension": ["js", "ts"],
Expand Down
1 change: 1 addition & 0 deletions test/mocha_mongodb.json
Expand Up @@ -4,6 +4,7 @@
"source-map-support/register",
"ts-node/register",
"test/tools/runner/chai-addons.js",
"test/tools/runner/hooks/resource_check.fixture.ts",
"test/tools/runner/hooks/configuration.js",
"test/tools/runner/hooks/client_leak_checker.js",
"test/tools/runner/hooks/session_leak_checker.js"
Expand Down
4 changes: 2 additions & 2 deletions test/tools/reporter/mongodb_reporter.js
Expand Up @@ -154,9 +154,9 @@ class MongoDBMochaReporter extends mocha.reporters.Spec {
fs.writeFileSync('xunit.xml', outputToXML(output), { encoding: 'utf8' });
}
this.xunitWritten = true;
console.log(chalk.bold('wrote xunit.xml'));
console.log(chalk.bold(' wrote xunit.xml'));
} catch (error) {
console.error(chalk.red(`Failed to output xunit report! ${error}`));
console.error(chalk.red(` Failed to output xunit report! ${error}`));
} finally {
if (ctrlC) process.exit(1);
}
Expand Down
96 changes: 96 additions & 0 deletions test/tools/runner/hooks/resource_check.fixture.ts
@@ -0,0 +1,96 @@
import { expect } from 'chai';
import * as chalk from 'chalk';

///// Test the memory leak checker with the following:
// const serializeSpy = require('sinon').spy(require('bson'), 'serialize');

let startingMemoryUsage: NodeJS.MemoryUsage;

declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace NodeJS {
interface Process {
_getActiveHandles(): ReadonlyArray<{
fd?: number;
_peername?: {
address: string;
family: string;
port: number;
};
}>;
_getActiveRequests(): unknown[];
}
}
}

const getActiveHandles = () => {
const handles = process._getActiveHandles();
const results = { files: [], sockets: [] };
for (const { fd, _peername: peer } of handles) {
if (typeof fd === 'number') {
results.files.push(fd);
} else {
results.sockets.push(peer);
}
}
return results;
};

const toMBString = (byteCount: number) => `${(byteCount / 1000 ** 2).toFixed(3)} MB`;

function mochaGlobalSetup() {
const activeHandles = getActiveHandles();
const activeRequests = process._getActiveRequests();

expect(activeHandles.files).to.have.lengthOf.lessThanOrEqual(3); // stdin/out/err
expect(activeHandles.sockets).to.have.lengthOf(0);
expect(activeRequests).to.have.a.lengthOf(0);

startingMemoryUsage = process.memoryUsage();
}

function mochaGlobalTeardown() {
const shutdownMemoryUsage = process.memoryUsage();
const activeHandles = getActiveHandles();
const activeRequests = process._getActiveRequests();

const startupHeapUsed = startingMemoryUsage.heapUsed;
const shutdownHeapUsed = shutdownMemoryUsage.heapUsed;
const memoryMessage = [
` startup heapUsed: ${toMBString(startupHeapUsed)}`,
` shutdown heapUsed: ${toMBString(shutdownHeapUsed)}`
].join('\n');
console.log(`${chalk.yellow(memoryMessage)}\n`);

if (process.platform === 'darwin' || process.env.ATLAS_DATA_LAKE === 'true') {
// TODO(NODE-XXXX): on macos we don't check for leaks currently
// TODO(NODE-XXXX): ADL tests have a remaining connection at the end of the test run but it does not cause the process to hang
return;
}

try {
expect(activeHandles.files).to.have.lengthOf.lessThanOrEqual(3); // stdin/out/err
expect(activeHandles.sockets).to.have.lengthOf(0);
expect(activeRequests).to.have.a.lengthOf(0);
// Very generous check to quadruple memory usage by the end of testing
// should catch wildly unbounded allocations only
// (technically the garbage collector may never be run, but this was observed to be the least flakey)
expect(
shutdownHeapUsed,
`${toMBString(shutdownHeapUsed)} is more than 4x ${toMBString(startupHeapUsed)}`
).to.be.lessThan(startupHeapUsed * 4);
} catch (error) {
console.error(error.message);
console.error(error.stack);
process.exit(1);
}

setTimeout(() => {
console.error(
'Nodejs is still open after 30 seconds of completing the test run there must be a resource leak'
);
process.exit(1);
}, 30_000).unref();
}

module.exports = { mochaGlobalTeardown, mochaGlobalSetup };