Skip to content

Commit 4dc6b4c

Browse files
dario-piotrowiczaduh95
authored andcommitted
watch: add --watch-kill-signal flag
add the new `--watch-kill-signal` to allow users to customize what signal is sent to the process on restarts during watch mode PR-URL: #58719 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent e24dede commit 4dc6b4c

File tree

6 files changed

+147
-2
lines changed

6 files changed

+147
-2
lines changed

doc/api/cli.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3080,6 +3080,19 @@ mode. If no file is provided, Node.js will exit with status code `9`.
30803080
node --watch index.js
30813081
```
30823082

3083+
### `--watch-kill-signal`
3084+
3085+
<!-- YAML
3086+
added:
3087+
- REPLACEME
3088+
-->
3089+
3090+
Customizes the signal sent to the process on watch mode restarts.
3091+
3092+
```bash
3093+
node --watch --watch-kill-signal SIGINT test.js
3094+
```
3095+
30833096
### `--watch-path`
30843097

30853098
<!-- YAML
@@ -3422,6 +3435,7 @@ one is included in the list below.
34223435
* `--use-openssl-ca`
34233436
* `--use-system-ca`
34243437
* `--v8-pool-size`
3438+
* `--watch-kill-signal`
34253439
* `--watch-path`
34263440
* `--watch-preserve-output`
34273441
* `--watch`

doc/node-config-schema.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,9 @@
568568
"watch": {
569569
"type": "boolean"
570570
},
571+
"watch-kill-signal": {
572+
"type": "string"
573+
},
571574
"watch-path": {
572575
"oneOf": [
573576
{

lib/internal/main/watch_mode.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const {
2020
const { getOptionValue } = require('internal/options');
2121
const { FilesWatcher } = require('internal/watch_mode/files_watcher');
2222
const { green, blue, red, white, clear } = require('internal/util/colors');
23+
const { convertToValidSignal } = require('internal/util');
2324

2425
const { spawn } = require('child_process');
2526
const { inspect } = require('util');
@@ -30,8 +31,7 @@ const { once } = require('events');
3031
prepareMainThreadExecution(false, false);
3132
markBootstrapComplete();
3233

33-
// TODO(MoLow): Make kill signal configurable
34-
const kKillSignal = 'SIGTERM';
34+
const kKillSignal = convertToValidSignal(getOptionValue('--watch-kill-signal'));
3535
const kShouldFilterModules = getOptionValue('--watch-path').length === 0;
3636
const kEnvFile = getOptionValue('--env-file') || getOptionValue('--env-file-if-exists');
3737
const kWatchedPaths = ArrayPrototypeMap(getOptionValue('--watch-path'), (path) => resolve(path));

src/node_options.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,11 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
862862
"path to watch",
863863
&EnvironmentOptions::watch_mode_paths,
864864
kAllowedInEnvvar);
865+
AddOption("--watch-kill-signal",
866+
"kill signal to send to the process on watch mode restarts"
867+
"(default: SIGTERM)",
868+
&EnvironmentOptions::watch_mode_kill_signal,
869+
kAllowedInEnvvar);
865870
AddOption("--watch-preserve-output",
866871
"preserve outputs on watch mode restart",
867872
&EnvironmentOptions::watch_mode_preserve_output,

src/node_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ class EnvironmentOptions : public Options {
229229
bool watch_mode = false;
230230
bool watch_mode_report_to_parent = false;
231231
bool watch_mode_preserve_output = false;
232+
std::string watch_mode_kill_signal = "SIGTERM";
232233
std::vector<std::string> watch_mode_paths;
233234

234235
bool syntax_check_only = false;
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import * as common from '../common/index.mjs';
2+
import { describe, it, beforeEach } from 'node:test';
3+
import { once } from 'node:events';
4+
import assert from 'node:assert';
5+
import { spawn } from 'node:child_process';
6+
import { writeFileSync } from 'node:fs';
7+
import tmpdir from '../common/tmpdir.js';
8+
9+
if (common.isWindows) {
10+
common.skip('no signals on Windows');
11+
}
12+
13+
if (common.isIBMi) {
14+
common.skip('IBMi does not support `fs.watch()`');
15+
}
16+
17+
if (common.isAIX) {
18+
common.skip('folder watch capability is limited in AIX.');
19+
}
20+
21+
const indexContents = `
22+
const { setTimeout } = require("timers/promises");
23+
(async () => {
24+
// Wait a few milliseconds to make sure that the
25+
// parent process has time to attach its listeners
26+
await setTimeout(200);
27+
28+
process.on('SIGTERM', () => {
29+
console.log('__SIGTERM received__');
30+
process.exit(123);
31+
});
32+
33+
process.on('SIGINT', () => {
34+
console.log('__SIGINT received__');
35+
process.exit(124);
36+
});
37+
38+
console.log('ready!');
39+
40+
// Wait for a long time (just to keep the process alive)
41+
await setTimeout(100_000_000);
42+
})();
43+
`;
44+
45+
let indexPath = '';
46+
47+
function refresh() {
48+
tmpdir.refresh();
49+
indexPath = tmpdir.resolve('index.js');
50+
writeFileSync(indexPath, indexContents);
51+
}
52+
53+
describe('test runner watch mode with --watch-kill-signal', () => {
54+
beforeEach(refresh);
55+
56+
it('defaults to SIGTERM', async () => {
57+
let currentRun = Promise.withResolvers();
58+
const child = spawn(process.execPath, ['--watch', indexPath], {
59+
cwd: tmpdir.path,
60+
});
61+
62+
let stdout = '';
63+
child.stdout.on('data', (data) => {
64+
stdout += data.toString();
65+
currentRun.resolve();
66+
});
67+
68+
await currentRun.promise;
69+
70+
currentRun = Promise.withResolvers();
71+
writeFileSync(indexPath, indexContents);
72+
73+
await currentRun.promise;
74+
child.kill();
75+
const [exitCode] = await once(child, 'exit');
76+
assert.match(stdout, /__SIGTERM received__/);
77+
assert.strictEqual(exitCode, 123);
78+
});
79+
80+
it('can be overridden (to SIGINT)', async () => {
81+
let currentRun = Promise.withResolvers();
82+
const child = spawn(process.execPath, ['--watch', '--watch-kill-signal', 'SIGINT', indexPath], {
83+
cwd: tmpdir.path,
84+
});
85+
let stdout = '';
86+
87+
child.stdout.on('data', (data) => {
88+
stdout += data.toString();
89+
if (stdout.includes('ready!')) {
90+
currentRun.resolve();
91+
}
92+
});
93+
94+
await currentRun.promise;
95+
96+
currentRun = Promise.withResolvers();
97+
writeFileSync(indexPath, indexContents);
98+
99+
await currentRun.promise;
100+
child.kill();
101+
const [exitCode] = await once(child, 'exit');
102+
assert.match(stdout, /__SIGINT received__/);
103+
assert.strictEqual(exitCode, 124);
104+
});
105+
106+
it('errors if an invalid signal is provided', async () => {
107+
const currentRun = Promise.withResolvers();
108+
const child = spawn(process.execPath, ['--watch', '--watch-kill-signal', 'invalid_signal', indexPath], {
109+
cwd: tmpdir.path,
110+
});
111+
let stdout = '';
112+
113+
child.stderr.on('data', (data) => {
114+
stdout += data.toString();
115+
currentRun.resolve();
116+
});
117+
118+
await currentRun.promise;
119+
120+
assert.match(stdout, new RegExp(/TypeError \[ERR_UNKNOWN_SIGNAL\]: Unknown signal: invalid_signal/));
121+
});
122+
});

0 commit comments

Comments
 (0)