Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const Sentry = require('@sentry/node');

// Unreachable rather than invalid, so `client.close()` is still pending while the
// broken pipe keeps erroring.
Sentry.init({
traceLifecycle: 'static',
dsn: 'https://public@127.0.0.1:1/1337',
});

// The test runner closes both stdio streams, so this write raises EPIPE. Node ignores
// SIGPIPE, so it arrives as an uncaught exception.
setInterval(() => process.stdout.write('x'.repeat(4096)), 0);
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,26 @@ describe('OnUncaughtException integration', () => {
});
}));

test('should exit rather than recurse when stderr is a broken pipe', async () => {
const testScriptPath = path.resolve(__dirname, 'broken-stdio-pipe-test-script.js');

// The heap cap makes a regression fail in ~1s; at the default size it takes a minute
// and just looks like a hang.
const child = childProcess.spawn(process.execPath, ['--max-old-space-size=64', testScriptPath], {
stdio: ['ignore', 'pipe', 'pipe'],
});

child.stdout.destroy();
child.stderr.destroy();

const exited = await new Promise<{ code: number | null; signal: string | null }>(resolve => {
child.on('exit', (code, signal) => resolve({ code, signal }));
});

// Unbounded recursion shows up as SIGABRT from the V8 OOM abort.
expect(exited).toEqual({ code: 1, signal: null });
});

describe('with `exitEvenIfOtherHandlersAreRegistered` set to false', () => {
test('should close process on uncaught error with no additional listeners registered', () =>
new Promise<void>(done => {
Expand Down
10 changes: 10 additions & 0 deletions packages/node/src/utils/errorhandling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,19 @@ import type { NodeClient } from '../sdk/client';

const DEFAULT_SHUTDOWN_TIMEOUT = 2000;

let isShuttingDown = false;

/**
* @hidden
*/
export function logAndExitProcess(error: unknown): void {
// A broken stderr makes the console write below raise EPIPE, re-entering here. Return
// rather than exit, so the in-flight `client.close()` still flushes the fatal event.
if (isShuttingDown) {
return;
}
isShuttingDown = true;

consoleSandbox(() => {
// eslint-disable-next-line no-console
console.error(error);
Expand All @@ -33,6 +42,7 @@ export function logAndExitProcess(error: unknown): void {
},
error => {
DEBUG_BUILD && debug.error(error);
global.process.exit(1);
},
);
}
Loading