Skip to content

feat(monitors): graceful shutdown over the control socket. - #869

Draft
Anamika1608 wants to merge 6 commits into
urunc-dev:mainfrom
Anamika1608:graceful-shutdown-kill-path
Draft

feat(monitors): graceful shutdown over the control socket.#869
Anamika1608 wants to merge 6 commits into
urunc-dev:mainfrom
Anamika1608:graceful-shutdown-kill-path

Conversation

@Anamika1608

@Anamika1608 Anamika1608 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Description

Adds an opt-in graceful shutdown for the monitors that expose a control socket. When graceful_shutdown is enabled for a monitor and the container receives SIGTERM, urunc asks the monitor to inject its native guest-shutdown event over the control socket instead of killing the monitor:

  • QEMU: system_powerdown over QMP
  • Cloud Hypervisor: vm.power-button over the REST API
  • Firecracker: SendCtrlAltDel, x86 only (SupportsGuestShutdown() gates it by
    architecture; on aarch64 Firecracker has no such mechanism)

urunc sends the request and returns without forwarding the signal. It does not wait and adds no timeout of its own: the container manager already escalates to SIGKILL after its grace period. Any error, an unsupported monitor, the feature
being disabled, or any signal other than SIGTERM falls back to the exact kill behavior of today.

This is the runtime ("host") side of graceful shutdown. Teaching the guest init (urunit) to react to these events is a separate follow-up; until then the events are delivered but the guest does not yet act on them.

The feature is off by default. It is wired as a per-monitor graceful_shutdown boolean, following the existing vhost option pattern.

Note: this branch is stacked on #850 (the configurable control socket). The
first commit here (feat(monitors): expose a configurable control socket for each monitor) belongs to #850. Once #850 merges, this branch will be rebased
onto main so the diff shows only the graceful-shutdown commits.

Related issues

How was this tested?

  • Unit tests for the new interface methods, driving fake QMP and REST servers over Unix sockets, including a test that asserts the QMP client drains the asynchronous POWERDOWN event and reads the command's return (a single-read client fails it).
  • A manual end-to-end matrix on aarch64 (QEMU and Cloud Hypervisor), covering: the happy path (the event is sent and urunc returns; the monitor keeps running until the container manager's SIGKILL, since urunit does not react yet); opt-out parity (feature off behaves exactly as before); a dead monitor and a
    SIGSTOP'd monitor both falling back safely without hanging; a custom socket_path; a non-SIGTERM signal never attempting graceful shutdown; and Firecracker on aarch64 taking the unsupported path.
  • The clearest signal is stop latency: with the feature on, nerdctl stop takes the full ~10s grace period (urunc sends the event and returns, the monitor survives until SIGKILL); with it off, the same stop is ~0.06s.

LLM usage

claude code (opus 4.8) for the understanding of codebase, approach decisions and reviews

Checklist

  • I have read the contribution guide.
  • The linter passes locally (make lint).
  • The e2e tests of at least one tool pass locally (make test_ctr, make test_nerdctl, make test_docker, make test_crictl).
  • If LLMs were used: I have read the llm policy.

Expose each monitor's control socket in the normal boot flow, so the
runtime can keep talking to the VMM after the guest starts. Every
monitor boots exactly as before; the only change is that its control
socket stays open and reachable:

- Firecracker launches with --api-sock instead of --no-api, keeping
  --config-file so the guest still boots from the config file.
- QEMU exposes a QMP Unix socket in server mode, configured not to
  wait for a client before booting, alongside the disabled human
  monitor.
- Cloud Hypervisor exposes its REST API socket (--api-socket).

The socket location is configurable through a new socket_path option
under a monitor's configuration, wired through MonitorConfig, ExecArgs
and the state.json annotation passthrough, with a per-container default
of /tmp/<id>.sock behind a DefaultSocketDir constant and a shared
resolveSocketPath helper. After changeRoot, urunc creates the socket
path's directory inside the monitor rootfs, so any custom path works;
it fails only if the location is invalid, such as a file already
existing at one of the path's components.

Extend the QEMU BuildExecCmd tests to cover the new argument and
document the socket_path option.

Signed-off-by: Anamika Aggarwal <anamikaagg18@gmail.com>
Add an opt-in graceful_shutdown boolean to a monitor's configuration,
wired through MonitorConfig and the state.json annotation passthrough
the same way as the existing vhost option. It is inert in this commit.

Signed-off-by: Anamika Aggarwal <anamikaagg18@gmail.com>
On SIGTERM, when a monitor exposes a control socket and graceful_shutdown
is enabled, urunc asks the monitor to inject its native guest-shutdown
event over the socket instead of killing the monitor: QEMU
system power-down, Cloud Hypervisor vm.power-button, and Firecracker
SendCtrlAltDel on x86. The socket lives inside the monitor's rootfs, so
it is reached through /proc/<pid>/root. urunc then returns and lets the
container manager escalate to SIGKILL on its own grace period.

Any error, an unsupported monitor, a disabled feature, or any signal
other than SIGTERM falls back to forwarding the signal exactly as
before. Add SupportsGuestShutdown and RequestGuestShutdown to the VMM
interface with per-monitor implementations and unit tests driving fake
QMP and REST servers.

Signed-off-by: Anamika Aggarwal <anamikaagg18@gmail.com>
Add a net.Pipe based test whose fully synchronous writes make a single-read
client block a follow-up command, guarding the QMP client's read-until-return
behavior: a regression that stopped at the asynchronous power-down event
instead of the command's own return would fail the suite.

Signed-off-by: Anamika Aggarwal <anamikaagg18@gmail.com>
Add a graceful_shutdown row to the Monitor Options table, describing the
opt-in SIGTERM behavior and its per-monitor support (QEMU and Cloud
Hypervisor on all architectures, Firecracker on x86 only).

Signed-off-by: Anamika Aggarwal <anamikaagg18@gmail.com>
Four small safety fixes to the graceful guest-shutdown request path:
- bound the whole QMP attempt (dial plus exchange) by a single deadline
  budget, instead of a dial timeout and an exchange timeout stacking
- disable HTTP keep-alive on the unix socket client, so no idle connection
  lingers in the pool if this helper is reused from a long-lived process
- cap the error-body read from the monitor at 4096 bytes
- log the case where graceful shutdown is enabled but the monitor does not
  support it, making the fall-through observable

Signed-off-by: Anamika Aggarwal <anamikaagg18@gmail.com>
@netlify

netlify Bot commented Jul 31, 2026

Copy link
Copy Markdown

Deploy Preview for urunc ready!

Name Link
🔨 Latest commit 60429ad
🔍 Latest deploy log https://app.netlify.com/projects/urunc/deploys/6a6c9d08de4b43000877fd66
😎 Deploy Preview https://deploy-preview-869--urunc.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant