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

fix(node): Enforce that ContextLines integration does not leave open file handles #14995

Merged
merged 1 commit into from
Jan 14, 2025
Merged
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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott

Work in this release was contributed by @nwalters512, @aloisklink, @arturovt, @benjick and @maximepvrt. Thank you for your contributions!
Work in this release was contributed by @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, and @mstrokin. Thank you for your contributions!

- **feat(solidstart)!: Default to `--import` setup and add `autoInjectServerSentry` ([#14862](https://github.com/getsentry/sentry-javascript/pull/14862))**

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { join } from 'path';
import { createRunner } from '../../utils/runner';
import { createRunner } from '../../../utils/runner';

describe('ContextLines integration in ESM', () => {
test('reads encoded context lines from filenames with spaces', done => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as Sentry from '@sentry/node';

export function captureException(i: number): void {
Sentry.captureException(new Error(`error in loop ${i}`));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { captureException } from './nested-file';

export function runSentry(): void {
for (let i = 0; i < 10; i++) {
captureException(i);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { execSync } from 'node:child_process';
import * as path from 'node:path';

import { loggingTransport } from '@sentry-internal/node-integration-tests';
import * as Sentry from '@sentry/node';

Sentry.init({
dsn: 'https://[email protected]/1337',
release: '1.0',
transport: loggingTransport,
});

import { runSentry } from './other-file';

runSentry();

const lsofOutput = execSync(`lsof -p ${process.pid}`, { encoding: 'utf8' });
const lsofTable = lsofOutput.split('\n');
const mainPath = __dirname.replace(`${path.sep}suites${path.sep}contextLines${path.sep}memory-leak`, '');
const numberOfLsofEntriesWithMainPath = lsofTable.filter(entry => entry.includes(mainPath));

// There should only be a single entry with the main path, otherwise we are leaking file handles from the
// context lines integration.
if (numberOfLsofEntriesWithMainPath.length > 1) {
// eslint-disable-next-line no-console
console.error('Leaked file handles detected');
// eslint-disable-next-line no-console
console.error(lsofTable);
process.exit(1);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { cleanupChildProcesses, createRunner } from '../../../utils/runner';

describe('ContextLines integration in CJS', () => {
afterAll(() => {
cleanupChildProcesses();
});

// Regression test for: https://github.com/getsentry/sentry-javascript/issues/14892
test('does not leak open file handles', done => {
createRunner(__dirname, 'scenario.ts')
.expectN(10, {
event: {},
})
.start(done);
});
});
213 changes: 213 additions & 0 deletions dev-packages/node-integration-tests/test.txt

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions dev-packages/node-integration-tests/utils/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ export function createRunner(...paths: string[]) {
expectedEnvelopes.push(expected);
return this;
},
expectN: function (n: number, expected: Expected) {
for (let i = 0; i < n; i++) {
expectedEnvelopes.push(expected);
}
return this;
},
expectHeader: function (expected: ExpectedEnvelopeHeader) {
if (!expectedEnvelopeHeaders) {
expectedEnvelopeHeaders = [];
Expand Down
14 changes: 11 additions & 3 deletions packages/node/src/integrations/contextlines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,21 @@ function getContextLinesFromFile(path: string, ranges: ReadlineRange[], output:
input: stream,
});

// We need to explicitly destroy the stream to prevent memory leaks,
// removing the listeners on the readline interface is not enough.
// See: https://github.com/nodejs/node/issues/9002 and https://github.com/getsentry/sentry-javascript/issues/14892
function destroyStreamAndResolve(): void {
stream.destroy();
resolve();
}

// Init at zero and increment at the start of the loop because lines are 1 indexed.
let lineNumber = 0;
let currentRangeIndex = 0;
const range = ranges[currentRangeIndex];
if (range === undefined) {
// We should never reach this point, but if we do, we should resolve the promise to prevent it from hanging.
resolve();
destroyStreamAndResolve();
return;
}
let rangeStart = range[0];
Expand All @@ -162,14 +170,14 @@ function getContextLinesFromFile(path: string, ranges: ReadlineRange[], output:
DEBUG_BUILD && logger.error(`Failed to read file: ${path}. Error: ${e}`);
lineReaded.close();
lineReaded.removeAllListeners();
resolve();
destroyStreamAndResolve();
}

// We need to handle the error event to prevent the process from crashing in < Node 16
// https://github.com/nodejs/node/pull/31603
stream.on('error', onStreamError);
lineReaded.on('error', onStreamError);
lineReaded.on('close', resolve);
lineReaded.on('close', destroyStreamAndResolve);

lineReaded.on('line', line => {
lineNumber++;
Expand Down
Loading