Skip to content

fix(windows): end sandbox processes when the gateway is killed - #5299

Open
ashwind-nvidia wants to merge 1 commit into
NVIDIA:windowsfrom
ashwind-nvidia:fix/5298-gateway-kill-sandboxes/ashwind-nvidia
Open

ashwind-nvidia wants to merge 1 commit into
NVIDIA:windowsfrom
ashwind-nvidia:fix/5298-gateway-kill-sandboxes/ashwind-nvidia

Conversation

@ashwind-nvidia

Copy link
Copy Markdown

Summary

With the MXC driver built into the gateway, hard-killing the gateway left its sandbox processes running with no owner, and they kept the gateway's listening socket open so a new gateway could not bind the port. The gateway now puts itself in a kill-on-close job object so every sandbox process ends with it, and its listening sockets are no longer inheritable, so no child can keep the port bound.

Related Issue

Closes #5298

Changes

  • Add openshell_gateway::process_lifetime::kill_children_on_exit(), called first in the gateway's main. It puts the process in a job object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; every sandbox process joins it and Windows ends them when the gateway exits for any reason. If the job cannot be created, a warning is printed and the gateway continues. It does nothing on other platforms.
  • Clear the inherit flag on the gateway, health and metrics listening sockets right after bind (disable_handle_inheritance). mio 1.2.0 creates Windows sockets with plain socket(), which leaves the handle inheritable, and children are started with handle inheritance enabled.
  • Add the windows crate as a Windows-only dependency of openshell-server and openshell-gateway (Cargo.lock updated).
  • Two unit tests for the listener (not inheritable, still accepts connections) and one for the job object (the process is in a kill-on-close job).

Behavior change: sandboxes now end when the gateway process exits, including a normal restart. Before, they were left running without an owner.

Testing

  • Checks appropriate to the affected code and behavior pass.
  • Unit tests added: gateway_listener::tests (2) and process_lifetime::tests (1) pass on Windows x64; the server tests that ran alongside (4) pass.
  • Native reproduction on Windows 11 x64 with three running sandboxes. Before: hard-killing the gateway left 9 processes (3 each of wxc-exec, openshell-supervisor, openshell-windows-sandbox), the port could not be bound, and a new gateway failed with os error 10048, in 5 of 5 runs. After: no process survived, the port could be bound and a new gateway started in 3 of 3 runs on the final build, and in 5 of 5 runs on an earlier build that differed only by two lint-driven edits.
  • Each change on its own, using a temporary test build with switches that turned the other off (not committed): the job object alone leaves no survivors and a free port (2 of 2 runs); the non-inheritable listener alone leaves the processes running but the port free (2 of 2 runs).
  • Existing MXC end-to-end scenarios (run-mxc-e2e.ps1): 6 of 7 pass with and without the change; host-service fails in both, so it is not caused by this change. The runner's port probe did not detect the listening gateway on this host, so a scratch copy with a loopback connect probe was used; the repository copy was not modified.
  • cargo fmt --all -- --check passes. cargo clippy with the CI flags reports 322 diagnostics (319 large future) both with and without this change and none in the changed files. cargo check --locked and cargo clippy --locked --all-targets -D warnings pass for openshell-gateway and openshell-server on Linux (Rust 1.95.0).

Not tested: ARM64, a graceful gateway shutdown, and the full workspace test suite.

Checklist

  • Follows Conventional Commits.
  • Commits are signed off (DCO).
  • Architecture docs updated (if applicable): no architecture change.

When the MXC driver runs inside the gateway, a hard-killed gateway left
its sandbox processes (wxc-exec, openshell-supervisor and
openshell-windows-sandbox) running with no owner. They also kept the
gateway's listening socket open, so the port stayed bound and a new
gateway failed to start with "Only one usage of each socket address"
(os error 10048).

There were two causes, so there are two changes:

- The gateway now puts itself in a kill-on-close job object before it
  spawns anything. Every sandbox process joins the job, and Windows ends
  them all when the gateway process exits for any reason, including a
  hard kill. If the job cannot be created the gateway logs a warning and
  continues.
- mio creates listening sockets with plain socket(), which leaves the
  handle inheritable, and child processes are started with handle
  inheritance enabled, so each sandbox process held a copy of the
  socket. The gateway, health and metrics listeners now clear the inherit
  flag right after bind.

Either change releases the port on its own; the job object also removes
the orphaned processes.

Tests: unit tests check that the process is in a kill-on-close job and
that a bound gateway listener is not inheritable and still accepts
connections. On a native Windows 11 x64 host with three running
sandboxes, hard-killing the gateway left 9 processes and an unbindable
port in 5 of 5 runs before this change. With it, no process survived,
the port could be bound and a new gateway started in 3 of 3 runs on the
final build, and in 5 of 5 runs on an earlier build that differed only by
two lint-driven edits. The existing MXC end-to-end scenarios give the
same results with and without the change.

Signed-off-by: ashwind <ashwind@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Nov 5, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

All contributors have signed the DCO ✍️ ✅
Posted by the DCO Assistant Lite bot.

@ashwind-nvidia

Copy link
Copy Markdown
Author

I have read the DCO document and I hereby sign the DCO.

This branch has not been deployed

No deployments
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.

bug(windows): sandbox processes outlive a killed gateway and keep its port bound

1 participant