Skip to content

Commit 463fe97

Browse files
Merge branch 'nodejs:main' into patch-1
2 parents 7a1a677 + 4d5ee24 commit 463fe97

File tree

8 files changed

+162
-2
lines changed

8 files changed

+162
-2
lines changed

doc/api/cli.md

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

3135+
### `--watch-kill-signal`
3136+
3137+
<!-- YAML
3138+
added:
3139+
- REPLACEME
3140+
-->
3141+
3142+
Customizes the signal sent to the process on watch mode restarts.
3143+
3144+
```bash
3145+
node --watch --watch-kill-signal SIGINT test.js
3146+
```
3147+
31353148
### `--watch-path`
31363149

31373150
<!-- YAML
@@ -3474,6 +3487,7 @@ one is included in the list below.
34743487
* `--use-openssl-ca`
34753488
* `--use-system-ca`
34763489
* `--v8-pool-size`
3490+
* `--watch-kill-signal`
34773491
* `--watch-path`
34783492
* `--watch-preserve-output`
34793493
* `--watch`

doc/node-config-schema.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,9 @@
565565
"watch": {
566566
"type": "boolean"
567567
},
568+
"watch-kill-signal": {
569+
"type": "string"
570+
},
568571
"watch-path": {
569572
"oneOf": [
570573
{

doc/node.1

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,19 @@ Set V8's thread pool size which will be used to allocate background jobs.
621621
If set to 0 then V8 will choose an appropriate size of the thread pool based on the number of online processors.
622622
If the value provided is larger than V8's maximum, then the largest value will be chosen.
623623
.
624+
.It Fl -watch
625+
Starts Node.js in watch mode. When in watch mode, changes in the watched files cause the Node.js process to restart.
626+
627+
By default, watch mode will watch the entry point and any required or imported module. Use --watch-path to specify what paths to watch.
628+
.
629+
.It Fl -watch-path
630+
Starts Node.js in watch mode and specifies what paths to watch. When in watch mode, changes in the watched paths cause the Node.js process to restart.
631+
632+
This will turn off watching of required or imported modules, even when used in combination with --watch.
633+
.
634+
.It Fl -watch-kill-signal
635+
Customizes the signal sent to the process on watch mode restarts.
636+
.
624637
.It Fl -zero-fill-buffers
625638
Automatically zero-fills all newly allocated Buffer instances.
626639
.

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
@@ -957,6 +957,11 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
957957
"path to watch",
958958
&EnvironmentOptions::watch_mode_paths,
959959
kAllowedInEnvvar);
960+
AddOption("--watch-kill-signal",
961+
"kill signal to send to the process on watch mode restarts"
962+
"(default: SIGTERM)",
963+
&EnvironmentOptions::watch_mode_kill_signal,
964+
kAllowedInEnvvar);
960965
AddOption("--watch-preserve-output",
961966
"preserve outputs on watch mode restart",
962967
&EnvironmentOptions::watch_mode_preserve_output,

src/node_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ class EnvironmentOptions : public Options {
232232
bool watch_mode = false;
233233
bool watch_mode_report_to_parent = false;
234234
bool watch_mode_preserve_output = false;
235+
std::string watch_mode_kill_signal = "SIGTERM";
235236
std::vector<std::string> watch_mode_paths;
236237

237238
bool syntax_check_only = false;

test/parallel/test-fs-opendir.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ for (const bufferSize of ['', '1', null]) {
229229
async function doAsyncIterInvalidCallbackTest() {
230230
const dir = await fs.promises.opendir(testDir);
231231
assert.throws(() => dir.close('not function'), invalidCallbackObj);
232+
dir.close();
232233
}
233234
doAsyncIterInvalidCallbackTest().then(common.mustCall());
234235

@@ -257,6 +258,7 @@ doConcurrentAsyncAndSyncOps().then(common.mustCall());
257258
{
258259
const dir = fs.opendirSync(testDir);
259260
assert.throws(() => dir.read('INVALID_CALLBACK'), /ERR_INVALID_ARG_TYPE/);
261+
dir.close();
260262
}
261263

262264
// Check that concurrent read() operations don't do weird things.
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)