mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 18:25:13 +08:00
process.exit(), uncaught exceptions and unhandled rejections exit through Environment::Exit(), which never calls OnEnvironmentStopping(). If a Worker blocked in a synchronous native call held up that exit past the deadline, the watchdog blamed the main thread. Check Environment::is_stopping() and report that the process did not finish exiting instead. Signed-off-by: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Assisted-by: claude:opus-5.5 PR-URL: https://github.com/nodejs/node/pull/66361 Fixes: https://github.com/nodejs/node/issues/66360 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Filip Skokan <panva.ip@gmail.com>
84 lines
3.2 KiB
JavaScript
84 lines
3.2 KiB
JavaScript
'use strict';
|
|
|
|
// Tests that --process-timeout makes the process exit even if the main thread
|
|
// cannot be interrupted.
|
|
|
|
const common = require('../common');
|
|
const assert = require('assert');
|
|
const fs = require('fs');
|
|
const { spawnSync } = require('child_process');
|
|
const fixtures = require('../common/fixtures');
|
|
const tmpdir = require('../common/tmpdir');
|
|
|
|
tmpdir.refresh();
|
|
|
|
// The deadline is measured from the start of the process, so on a slow machine
|
|
// it can expire before the fixture has reached the state under test, which the
|
|
// fixture signals by creating a file. If it did not get that far and did not
|
|
// print the expected message, run it again with a longer timeout.
|
|
function runUntilReady(fixture, expected, args = []) {
|
|
for (let timeout = common.platformTimeout(1000); ; timeout *= 2) {
|
|
// Child processes of a previous attempt may still be running, so use a
|
|
// different file for each attempt.
|
|
const marker = tmpdir.resolve([fixture, ...args, timeout, 'ready'].join('.'));
|
|
const start = process.hrtime.bigint();
|
|
const child = spawnSync(process.execPath, [
|
|
`--process-timeout=${timeout}ms`,
|
|
fixtures.path('process-timeout', fixture),
|
|
marker,
|
|
...args,
|
|
], { encoding: 'utf8' });
|
|
const elapsed = Number(process.hrtime.bigint() - start) / 1e6;
|
|
|
|
if (!expected.test(child.stderr) && !fs.existsSync(marker) &&
|
|
timeout < common.platformTimeout(16000)) {
|
|
continue;
|
|
}
|
|
|
|
assert.strictEqual(child.signal, null);
|
|
assert.strictEqual(child.status, 124, child.stderr);
|
|
assert.ok(elapsed >= timeout, `Exited after ${elapsed}ms, before ${timeout}ms`);
|
|
assert.match(child.stderr, new RegExp(
|
|
`^\\(node:\\d+\\) Process timed out after ${timeout}ms \\(--process-timeout\\)\\. ` +
|
|
'Exiting with code 124\\.$', 'm'));
|
|
assert.match(child.stderr, expected);
|
|
return child.stderr;
|
|
}
|
|
}
|
|
|
|
{
|
|
// The main thread is blocked in a synchronous native call.
|
|
const stderr = runUntilReady(
|
|
'blocked-main-thread.js',
|
|
/^The main thread did not respond within 2000ms\. It is likely blocked/m);
|
|
assert.doesNotMatch(stderr, /Main thread was/);
|
|
}
|
|
|
|
{
|
|
// The main thread responds, but exiting waits for a Worker thread that is
|
|
// blocked in a synchronous native call.
|
|
const stderr = runUntilReady(
|
|
'blocked-worker.js',
|
|
/^\(node:\d+\) The process did not finish exiting within 5000ms after --process-timeout expired\. Forcing exit\.$/m);
|
|
assert.match(stderr, /^ {4}Worker \(thread 1, name 'blocked'\)$/m);
|
|
}
|
|
|
|
{
|
|
// The event loop has stopped, but exiting waits for a Worker thread that is
|
|
// blocked in a synchronous native call.
|
|
runUntilReady(
|
|
'blocked-worker-at-exit.js',
|
|
/^The process did not finish exiting after the event loop had stopped\.$/m);
|
|
}
|
|
|
|
for (const mode of ['exit', 'throw', 'reject']) {
|
|
// process.exit(), an uncaught exception, or an unhandled rejection waits for
|
|
// a Worker thread that is blocked in a synchronous native call. The main thread is exiting, so it
|
|
// must not be reported as blocked by the application.
|
|
const stderr = runUntilReady(
|
|
'blocked-worker-at-process-exit.js',
|
|
/^The process did not finish exiting after the event loop had stopped\.$/m,
|
|
[mode]);
|
|
assert.doesNotMatch(stderr, /The main thread did not respond/);
|
|
}
|