Goal
Currently `mirrorstack dev` calls `child.kill()` on Ctrl-C, which is SIGKILL on unix (TerminateProcess on windows). A Go server mid-DB-write loses any buffered work and any `signal.Notify` shutdown hooks the developer wired up never run.
Better: SIGTERM → wait ~5s with try_wait → SIGKILL fallback if it didn't exit.
Sketch
```rust
#[cfg(unix)]
fn signal_terminate(pid: u32) -> std::io::Result<()> {
use nix::sys::signal::{kill, Signal};
use nix::unistd::Pid;
kill(Pid::from_raw(pid as i32), Signal::SIGTERM).map_err(|e| std::io::Error::other(e))
}
#[cfg(windows)]
fn signal_terminate(_pid: u32) -> std::io::Result<()> {
// Windows has no SIGTERM; SIGKILL via child.kill() is the only option.
Ok(())
}
// In wait_or_interrupt's Ctrl-C arm:
let _ = signal_terminate(child.id());
match child.wait_timeout(Duration::from_secs(5))? {
Some(status) => return Ok(status),
None => {
let _ = child.kill();
return Ok(child.wait()?);
}
}
```
Acceptance
- Ctrl-C sends SIGTERM first; module's `signal.Notify` handler runs
- If module hasn't exited in 5s, escalate to SIGKILL
- Windows still works (TerminateProcess is the only path)
Deps
- `nix` crate (small; transitive via `ctrlc` already)
- `wait-timeout` crate, OR roll the wait loop in-house with try_wait
Estimate
~30 LoC + a 1-second test that asserts the timeout path. ~30 minutes.
Goal
Currently `mirrorstack dev` calls `child.kill()` on Ctrl-C, which is SIGKILL on unix (TerminateProcess on windows). A Go server mid-DB-write loses any buffered work and any `signal.Notify` shutdown hooks the developer wired up never run.
Better: SIGTERM → wait ~5s with try_wait → SIGKILL fallback if it didn't exit.
Sketch
```rust
#[cfg(unix)]
fn signal_terminate(pid: u32) -> std::io::Result<()> {
use nix::sys::signal::{kill, Signal};
use nix::unistd::Pid;
kill(Pid::from_raw(pid as i32), Signal::SIGTERM).map_err(|e| std::io::Error::other(e))
}
#[cfg(windows)]
fn signal_terminate(_pid: u32) -> std::io::Result<()> {
// Windows has no SIGTERM; SIGKILL via child.kill() is the only option.
Ok(())
}
// In wait_or_interrupt's Ctrl-C arm:
let _ = signal_terminate(child.id());
match child.wait_timeout(Duration::from_secs(5))? {
Some(status) => return Ok(status),
None => {
let _ = child.kill();
return Ok(child.wait()?);
}
}
```
Acceptance
Deps
Estimate
~30 LoC + a 1-second test that asserts the timeout path. ~30 minutes.