'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/); }