Skip to content

Commit 1cfc11f

Browse files
authored
feat: add kill timeout args (#35)
* feat: add kill timeout args * chore: update
1 parent 3536b23 commit 1cfc11f

File tree

8 files changed

+107
-46
lines changed

8 files changed

+107
-46
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ $ npx mwtsc --watch --run ./bootstrap.js
1414
# run with tsc options
1515
$ npx mwtsc --watch --project tsconfig.production.json --run ./bootstrap.js
1616

17+
# run with kill timeout
18+
$ npx mwtsc --watch --run ./bootstrap.js --kill-timeout 5000
19+
1720
# the child process keep avaliable during the development
1821
$ npx mwtsc --watch --run ./bootstrap.js --keepalive
1922

@@ -28,6 +31,15 @@ $ npx mwtsc --watch --inspect-brk --run ./bootstrap.js
2831
* 2、support copy non-ts file to dist directory when build source code
2932
* 3、support ts alias path by tsc-alias
3033

34+
35+
## About `--kill-timeout`
36+
37+
Process kill timeout in milliseconds. When restarting the application, if the process doesn't exit within this time, it will be forcefully killed.
38+
39+
Default: `2000`
40+
41+
When using `Ctrl+C` to stop the process, it will follow the `kill-timeout` to kill the process.
42+
3143
## About `--inspect` and `--inspect-brk`
3244

3345
If you're using VSCode or JetBrains IntelliJ IDEA (or other IDEs), you won't need to manually start the child process with debugging flags like --inspect or --inspect-brk. The IDE will automatically attach the debugger to the child process.

bin/mwtsc.js

100644100755
File mode changed.

lib/index.js

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -357,33 +357,41 @@ function run() {
357357
await customFn();
358358
};
359359

360-
async function onSignal() {
360+
function onSignal() {
361+
output(`\n${colors.green('Node.js server')} ${colors.dim(`will exit, and please wait patiently.`)}`);
362+
output(`${colors.dim(`You can shorten the waiting time by adjusting the `)}${colors.cyan('--kill-timeout')}${colors.dim(` parameter.`)}`);
361363
try {
362364
restart.clear();
363-
child.kill();
364-
if (runChild) {
365-
await runChild.kill();
366-
}
367-
if (proxyServer) {
368-
await proxyServer.close();
369-
}
370-
if (fileDeleteWatcher) {
371-
await fileDeleteWatcher.close();
372-
}
373-
await onExitHandler();
374-
process.exit(0);
365+
// 使用 Promise.all 并行执行所有清理任务
366+
return Promise.all([
367+
child.kill(),
368+
runChild && runChild.kill(),
369+
proxyServer && proxyServer.close(),
370+
fileDeleteWatcher && fileDeleteWatcher.close(),
371+
]).then(() => {
372+
onExitHandler().then(() => {
373+
process.exit(0);
374+
});
375+
});
375376
} catch (err) {
376377
console.error(err);
377-
await onExitHandler();
378-
process.exit(1);
378+
return onExitHandler().then(() => {
379+
process.exit(1);
380+
});
379381
}
380382
}
381383

382-
process.once('SIGINT', onSignal);
384+
process.once('SIGINT', () => {
385+
onSignal().catch(console.error); // 不再强制退出
386+
});
383387
// kill(3) Ctrl-\
384-
process.once('SIGQUIT', onSignal);
388+
process.once('SIGQUIT', () => {
389+
onSignal().catch(console.error); // 不再强制退出
390+
});
385391
// kill(15) default
386-
process.once('SIGTERM', onSignal);
392+
process.once('SIGTERM', () => {
393+
onSignal().catch(console.error); // 不再强制退出
394+
});
387395

388396
return {
389397
restart,

lib/output.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ class ConsoleOutput extends EventEmitter {
1818
}
1919
}
2020

21-
console.log('\nMidway Performance Statistics:');
22-
console.log(table.toString());
21+
output('\nMidway Performance Statistics:');
22+
output(table.toString());
2323
}
2424

2525
renderPerfStats(items) {
@@ -31,9 +31,9 @@ class ConsoleOutput extends EventEmitter {
3131
table.push([item.name, item.duration.toFixed(2)]);
3232
}
3333

34-
console.log('\nPerformance Statistics:');
35-
console.log(table.toString());
36-
console.log('');
34+
output('\nPerformance Statistics:');
35+
output(table.toString());
36+
output('');
3737
}
3838

3939
renderKeepAlive() {
@@ -54,7 +54,7 @@ class ConsoleOutput extends EventEmitter {
5454

5555
renderServerFirstReady(serverReportOption, during, hasPaths, debugUrl) {
5656
// 第一次启动把端口等信息打印出来
57-
console.log('');
57+
output('');
5858
output(
5959
`${colors.green('Node.js server')} ${colors.dim(
6060
'started in'
@@ -91,7 +91,7 @@ class ConsoleOutput extends EventEmitter {
9191
)}`
9292
);
9393
}
94-
console.log('');
94+
output('');
9595
}
9696
}
9797

lib/process.js

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -192,29 +192,62 @@ const forkRun = (runCmdPath, runArgs = [], options = {}) => {
192192

193193
innerFork(true);
194194

195-
const killRunningChild = async signal => {
196-
runChild.removeAllListeners('message');
197-
if (isWin) {
198-
await new Promise(resolve => {
199-
if (!runChild || runChild.exitCode !== null) {
200-
// has exited
201-
resolve();
195+
// 从参数中获取超时时间,默认 2000ms
196+
const killTimeout = (() => {
197+
const index = runArgs.indexOf('--kill-timeout');
198+
if (index !== -1 && runArgs[index + 1]) {
199+
const timeout = parseInt(runArgs[index + 1], 10);
200+
return isNaN(timeout) ? 2000 : timeout;
201+
}
202+
return 2000;
203+
})();
204+
205+
const killRunningChild = async () => {
206+
if (!runChild || runChild.exitCode !== null) {
207+
// 进程已退出
208+
debug('child process already exited');
209+
return;
210+
}
211+
212+
return new Promise(resolve => {
213+
const now = Date.now();
214+
debug(`send SIGINT to child process ${runChild.pid}`);
215+
// 发送退出消息给子进程
216+
runChild.send({
217+
title: 'server-kill',
218+
});
219+
220+
// 设置超时处理
221+
const timeoutHandle = setTimeout(() => {
222+
try {
223+
// 超时后强制结束进程
224+
debug(
225+
`send SIGKILL to child process ${runChild.pid} +${
226+
Date.now() - now
227+
}ms`
228+
);
229+
runChild.kill('SIGKILL');
230+
} catch (err) {
231+
debug(
232+
`send SIGKILL to child process error, msg = ${err.message}, pid = ${runChild.pid}`
233+
);
202234
}
203-
runChild.once('exit', (code, signal) => {
204-
resolve();
205-
});
206-
runChild.send({
207-
title: 'server-kill',
208-
});
235+
}, killTimeout);
236+
237+
// 监听进程退出
238+
runChild.once('exit', () => {
239+
debug(
240+
`child process ${runChild.pid} exited +${Date.now() - now}ms`
241+
);
242+
clearTimeout(timeoutHandle);
243+
resolve();
209244
});
210-
} else {
211-
runChild.kill(signal);
212-
}
245+
});
213246
};
214247

215248
return {
216-
async kill(signal) {
217-
await killRunningChild(signal);
249+
async kill() {
250+
await killRunningChild();
218251
},
219252
async restart() {
220253
// 杀进程

lib/util.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,11 +300,11 @@ exports.output = function (msg, datePadding = false) {
300300
timeStr = `[${exports.colors.dim(
301301
`${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`
302302
)}]`;
303-
console.log(`${timeStr} ${msg}`);
303+
process.stdout.write(`${timeStr} ${msg}\n`);
304304
return;
305305
}
306306

307-
console.log(msg);
307+
process.stdout.write(msg + '\n');
308308
};
309309

310310
exports.debug = function (msg) {

lib/wrap.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ const { join, isAbsolute } = require('path');
55
const { CHILD_PROCESS_EXCEPTION_EXIT_CODE } = require('./constants');
66
// eslint-disable-next-line node/no-unsupported-features/node-builtins
77
const inspector = require('inspector');
8+
const { debuglog } = require('util');
9+
10+
function debug(msg) {
11+
debuglog('[mwtsc]: ' + msg);
12+
};
13+
14+
const log = debuglog('midway:debug');
815

916
if (process.debugPort) {
1017
const debugUrl = inspector.url();
@@ -33,6 +40,7 @@ if (runArgs.includes('--keepalive')) {
3340

3441
process.on('message', data => {
3542
if (data.title === 'server-kill') {
43+
debug('send SIGINT to child process');
3644
process.emit('SIGINT');
3745
}
3846
});

test/index.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const { join, resolve } = require('path');
22
const { unlink } = require('fs/promises');
3-
const { existsSync, writeFileSync, readFileSync } = require('fs');
3+
const { existsSync, writeFileSync, readFileSync } = require('fs');
44
const { forkRun } = require('../lib/process');
55
const { execa, sleep, removeFile } = require('./util');
66
const fetch = require('node-fetch');

0 commit comments

Comments
 (0)