Skip to content

Commit

Permalink
feat: add kill timeout args (#35)
Browse files Browse the repository at this point in the history
* feat: add kill timeout args

* chore: update
  • Loading branch information
czy88840616 authored Dec 8, 2024
1 parent 3536b23 commit 1cfc11f
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 46 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ $ npx mwtsc --watch --run ./bootstrap.js
# run with tsc options
$ npx mwtsc --watch --project tsconfig.production.json --run ./bootstrap.js

# run with kill timeout
$ npx mwtsc --watch --run ./bootstrap.js --kill-timeout 5000

# the child process keep avaliable during the development
$ npx mwtsc --watch --run ./bootstrap.js --keepalive

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


## About `--kill-timeout`

Process kill timeout in milliseconds. When restarting the application, if the process doesn't exit within this time, it will be forcefully killed.

Default: `2000`

When using `Ctrl+C` to stop the process, it will follow the `kill-timeout` to kill the process.

## About `--inspect` and `--inspect-brk`

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.
Expand Down
Empty file modified bin/mwtsc.js
100644 → 100755
Empty file.
44 changes: 26 additions & 18 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -357,33 +357,41 @@ function run() {
await customFn();
};

async function onSignal() {
function onSignal() {
output(`\n${colors.green('Node.js server')} ${colors.dim(`will exit, and please wait patiently.`)}`);
output(`${colors.dim(`You can shorten the waiting time by adjusting the `)}${colors.cyan('--kill-timeout')}${colors.dim(` parameter.`)}`);
try {
restart.clear();
child.kill();
if (runChild) {
await runChild.kill();
}
if (proxyServer) {
await proxyServer.close();
}
if (fileDeleteWatcher) {
await fileDeleteWatcher.close();
}
await onExitHandler();
process.exit(0);
// 使用 Promise.all 并行执行所有清理任务
return Promise.all([
child.kill(),
runChild && runChild.kill(),
proxyServer && proxyServer.close(),
fileDeleteWatcher && fileDeleteWatcher.close(),
]).then(() => {
onExitHandler().then(() => {
process.exit(0);
});
});
} catch (err) {
console.error(err);
await onExitHandler();
process.exit(1);
return onExitHandler().then(() => {
process.exit(1);
});
}
}

process.once('SIGINT', onSignal);
process.once('SIGINT', () => {
onSignal().catch(console.error); // 不再强制退出
});
// kill(3) Ctrl-\
process.once('SIGQUIT', onSignal);
process.once('SIGQUIT', () => {
onSignal().catch(console.error); // 不再强制退出
});
// kill(15) default
process.once('SIGTERM', onSignal);
process.once('SIGTERM', () => {
onSignal().catch(console.error); // 不再强制退出
});

return {
restart,
Expand Down
14 changes: 7 additions & 7 deletions lib/output.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ class ConsoleOutput extends EventEmitter {
}
}

console.log('\nMidway Performance Statistics:');
console.log(table.toString());
output('\nMidway Performance Statistics:');
output(table.toString());
}

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

console.log('\nPerformance Statistics:');
console.log(table.toString());
console.log('');
output('\nPerformance Statistics:');
output(table.toString());
output('');
}

renderKeepAlive() {
Expand All @@ -54,7 +54,7 @@ class ConsoleOutput extends EventEmitter {

renderServerFirstReady(serverReportOption, during, hasPaths, debugUrl) {
// 第一次启动把端口等信息打印出来
console.log('');
output('');
output(
`${colors.green('Node.js server')} ${colors.dim(
'started in'
Expand Down Expand Up @@ -91,7 +91,7 @@ class ConsoleOutput extends EventEmitter {
)}`
);
}
console.log('');
output('');
}
}

Expand Down
69 changes: 51 additions & 18 deletions lib/process.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,29 +192,62 @@ const forkRun = (runCmdPath, runArgs = [], options = {}) => {

innerFork(true);

const killRunningChild = async signal => {
runChild.removeAllListeners('message');
if (isWin) {
await new Promise(resolve => {
if (!runChild || runChild.exitCode !== null) {
// has exited
resolve();
// 从参数中获取超时时间,默认 2000ms
const killTimeout = (() => {
const index = runArgs.indexOf('--kill-timeout');
if (index !== -1 && runArgs[index + 1]) {
const timeout = parseInt(runArgs[index + 1], 10);
return isNaN(timeout) ? 2000 : timeout;
}
return 2000;
})();

const killRunningChild = async () => {
if (!runChild || runChild.exitCode !== null) {
// 进程已退出
debug('child process already exited');
return;
}

return new Promise(resolve => {
const now = Date.now();
debug(`send SIGINT to child process ${runChild.pid}`);
// 发送退出消息给子进程
runChild.send({
title: 'server-kill',
});

// 设置超时处理
const timeoutHandle = setTimeout(() => {
try {
// 超时后强制结束进程
debug(
`send SIGKILL to child process ${runChild.pid} +${
Date.now() - now
}ms`
);
runChild.kill('SIGKILL');
} catch (err) {
debug(
`send SIGKILL to child process error, msg = ${err.message}, pid = ${runChild.pid}`
);
}
runChild.once('exit', (code, signal) => {
resolve();
});
runChild.send({
title: 'server-kill',
});
}, killTimeout);

// 监听进程退出
runChild.once('exit', () => {
debug(
`child process ${runChild.pid} exited +${Date.now() - now}ms`
);
clearTimeout(timeoutHandle);
resolve();
});
} else {
runChild.kill(signal);
}
});
};

return {
async kill(signal) {
await killRunningChild(signal);
async kill() {
await killRunningChild();
},
async restart() {
// 杀进程
Expand Down
4 changes: 2 additions & 2 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -300,11 +300,11 @@ exports.output = function (msg, datePadding = false) {
timeStr = `[${exports.colors.dim(
`${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`
)}]`;
console.log(`${timeStr} ${msg}`);
process.stdout.write(`${timeStr} ${msg}\n`);
return;
}

console.log(msg);
process.stdout.write(msg + '\n');
};

exports.debug = function (msg) {
Expand Down
8 changes: 8 additions & 0 deletions lib/wrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ const { join, isAbsolute } = require('path');
const { CHILD_PROCESS_EXCEPTION_EXIT_CODE } = require('./constants');
// eslint-disable-next-line node/no-unsupported-features/node-builtins
const inspector = require('inspector');
const { debuglog } = require('util');

function debug(msg) {
debuglog('[mwtsc]: ' + msg);
};

const log = debuglog('midway:debug');

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

process.on('message', data => {
if (data.title === 'server-kill') {
debug('send SIGINT to child process');
process.emit('SIGINT');
}
});
Expand Down
2 changes: 1 addition & 1 deletion test/index.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const { join, resolve } = require('path');
const { unlink } = require('fs/promises');
const { existsSync, writeFileSync, readFileSync } = require('fs');
const { existsSync, writeFileSync, readFileSync } = require('fs');
const { forkRun } = require('../lib/process');
const { execa, sleep, removeFile } = require('./util');
const fetch = require('node-fetch');
Expand Down

0 comments on commit 1cfc11f

Please sign in to comment.