fix(lifecycle): use ProcessLifecycle for coordinated shutdown

Replace per-Watcher SIGINT handlers with a single ProcessLifecycle.install()
in TsWatch.start(). This eliminates competing signal handler races that left
orphaned child processes. Add @push.rocks/smartexit as direct dependency.
This commit is contained in:
2026-03-03 23:43:26 +00:00
parent 91b3e273de
commit e8bd8da3c7
5 changed files with 34 additions and 42 deletions

View File

@@ -46,6 +46,14 @@ export class TsWatch {
public async start() {
logger.log('info', 'Starting tswatch with config-driven mode');
// Install global process lifecycle handlers (SIGINT, SIGTERM, etc.)
// This is the single authority for signal handling — no per-watcher handlers.
plugins.smartexit.ProcessLifecycle.install();
const exitInstance = new plugins.smartexit.SmartExit({ silent: true });
exitInstance.addCleanupFunction(async () => {
await this.stop();
});
// Start server if configured
if (this.config.server?.enabled) {
await this.startServer();

View File

@@ -181,34 +181,13 @@ export class Watcher {
}
/**
* this method sets up a clean exit strategy
* Sets up timeout-based cleanup if configured.
* Signal handling (SIGINT/SIGTERM) is managed globally by ProcessLifecycle in TsWatch.
*/
private async setupCleanup() {
// Last-resort synchronous cleanup — 'exit' event cannot await async work.
// By this point, SIGINT handler should have already called stop().
process.on('exit', () => {
if (this.currentExecution && !this.currentExecution.childProcess.killed) {
const pid = this.currentExecution.childProcess.pid;
if (pid) {
try {
process.kill(pid, 'SIGKILL');
} catch {
// Process may already be dead
}
}
}
});
process.on('SIGINT', async () => {
console.log('');
console.log('ok! got SIGINT We are exiting! Just cleaning up to exit neatly :)');
await this.stop();
process.exit(0);
});
// handle timeout
if (this.options.timeout) {
plugins.smartdelay.delayFor(this.options.timeout).then(async () => {
console.log(`timed out afer ${this.options.timeout} milliseconds! exiting!`);
console.log(`timed out after ${this.options.timeout} milliseconds! exiting!`);
await this.stop();
process.exit(0);
});

View File

@@ -16,6 +16,7 @@ import * as lik from '@push.rocks/lik';
import * as npmextra from '@push.rocks/npmextra';
import * as smartcli from '@push.rocks/smartcli';
import * as smartdelay from '@push.rocks/smartdelay';
import * as smartexit from '@push.rocks/smartexit';
import * as smartfs from '@push.rocks/smartfs';
import * as smartinteract from '@push.rocks/smartinteract';
import * as smartlog from '@push.rocks/smartlog';
@@ -29,6 +30,7 @@ export {
npmextra,
smartcli,
smartdelay,
smartexit,
smartfs,
smartinteract,
smartlog,