Skip to content

dev: SIGTERM-then-SIGKILL for graceful module shutdown #25

Description

@I-am-nothing

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions