Skip to content

fix(sandbox): keep idle execs and disconnect freezes off the shared blocking pool - #5217

Merged
FrostGod merged 2 commits into
NVIDIA:mainfrom
EmilienM:fix/5184-exec-blocking-pool/EmilienM
Nov 12, 2026
Merged

FrostGod merged 2 commits into
NVIDIA:mainfrom
EmilienM:fix/5184-exec-blocking-pool/EmilienM

Conversation

@EmilienM

@EmilienM EmilienM commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Exec pipes and PTY masters were wrapped in tokio::fs::File, so every idle read parked a blocking-pool thread. That pool is shared with the control plane (gRPC exchange, exec spawn/reap, AcceptNetwork), so enough idle execs starved new control connections. Losing the supervisor had a related problem: the freeze ran up to four synchronous /proc scans while holding the supervisor_connection mutex, which stalled a runtime worker and the lock.

Related Issue

Closes #5184.

Changes

  • A readiness-based AsyncFd adapter replaces tokio::fs::File for exec stdin/stdout/stderr and the PTY master; idle execs park no pool threads. Flush and shutdown keep the semantics consumers were written against, so nothing downstream changes.
  • Freeze and resume stop and continue the registered root groups synchronously (a confirmed recovery still implies the roots were continued) and defer the descendant /proc sweeps to a dedicated thread, so a full blocking pool cannot leave detached children running behind a boundary that reports itself frozen. A transition generation checked under a signal mutex drops stale batches, so a deferred SIGSTOP can never land after a resume SIGCONT. Terminal transitions keep their immediate group signaling and run their scans outside the mutex.
  • A transition that immediately follows another waits at most one signal application (pidfd re-validation and sends over an already-collected set), never a /proc scan.
  • A descendant that escapes a freeze or terminal signal is now reported: a low-severity OCSF process activity plus a detection finding, naming the recovery where one exists.
  • Out of scope on purpose: the per-exec reaper's single parked thread, sftp disk IO, and the per-connection exchange threads.

Release Notes

Testing

  • Checks appropriate to the affected code pass (clippy -D warnings, fmt, the full openshell-sandbox suite)
  • Unit tests added: a 12-idle-exec responsiveness test on a 16-thread capped pool (fails at the sixth exec without the fix), large-write and PTY-master read tests, stale-batch drop and delivery ordering tests, a full-blocking-pool freeze test with a setsid child, delivery-accounting and enforcement-gap event tests
  • e2e:podman:ci (38 tests) passes; openshell-sandbox-backend and openshell-supervisor-process suites pass

Checklist

  • Follows Conventional Commits
  • Commits are signed off (DCO)
  • Architecture docs updated (if applicable) (not applicable: the crate README's takeover section documents deadlines and fencing, which are unchanged)

@copy-pr-bot

copy-pr-bot Bot commented Nov 3, 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.

@mrunalp

mrunalp commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator

/ok to test 4b43d56

@mrunalp mrunalp added area:sandbox Sandbox runtime and isolation work test:e2e Requires end-to-end coverage labels Nov 4, 2026
@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown

Label test:e2e applied for 4b43d56. Open the existing run and click Re-run all jobs to execute with the label set. The run will execute the standard E2E suite after building the required gateway, sandbox, and supervisor images once. The matching required CI gate status on this PR will flip green automatically once the run finishes.

@mrunalp

mrunalp commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator

/ok to test 4b43d56

@FrostGod FrostGod left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a disconnect test with a full blocking pool and a setsid child? The current tests leave spare threads, so they miss this case.

@EmilienM
EmilienM force-pushed the fix/5184-exec-blocking-pool/EmilienM branch from 4b43d56 to 2065ac7 Compare November 10, 2026 16:02
@FrostGod

Copy link
Copy Markdown
Collaborator

/ok to test 2065ac7

@FrostGod
FrostGod enabled auto-merge November 10, 2026 19:27
@FrostGod

Copy link
Copy Markdown
Collaborator

@EmilienM , can u rebase the PR, seems like we need this #5223

…hreads

Exec stdin/stdout/stderr and the PTY master were wrapped in
tokio::fs::File, so every pending read on an idle pipe parked one tokio
blocking-pool thread until data arrived or the peer closed. That pool is
shared with the control plane (gRPC exchange, exec spawn/reap,
AcceptNetwork), and enough idle exec sessions exhausted it, blocking new
control connections.

Adopt the descriptors with a readiness-based AsyncFd adapter instead:
reads and writes only run when the descriptor is ready, and idle execs
park no pool threads. Flush and shutdown keep the tokio::fs::File
semantics the consumers were written against, and close-on-drop remains
the EOF mechanism, so no consumer changes.

An isolated boundary test caps the blocking pool at 16 threads, starts
12 idle execs, and asserts a fresh exec and a control request still
complete; without the fix it fails when the sixth idle exec stalls.

Signed-off-by: Emilien Macchi <emacchi@redhat.com>
transport_disconnected froze the workload while holding the
supervisor_connection mutex, and the freeze signaled every descendant
through up to four synchronous /proc scans, so losing the supervisor
stalled a runtime worker and the lock for as long as the scans took,
which grows with the number of processes in the sandbox.

Freeze and resume now stop and continue the registered root groups
synchronously, so a confirmed recovery still implies the roots were
continued, and defer the descendant sweeps to a dedicated thread: the
shared blocking pool can be fully occupied, and a sweep queued behind
it would leave detached children running while the boundary reports
itself frozen. If thread creation itself fails, the sweep runs
synchronously rather than leave the freeze unenforced. Each sweep
collects the owned set without holding any lock and applies the
signal under a signal mutex only while the transition generation it
captured is still current, so a stale deferred SIGSTOP can never land
after a resume SIGCONT. A sweep that fails to deliver reports the gap
as an OCSF process activity naming the recovery.

The scans now run under neither supervisor_connection nor the signal
mutex; a transition that immediately follows another waits at most one
signal application, never a scan.

Closes NVIDIA#5184

Signed-off-by: Emilien Macchi <emacchi@redhat.com>
auto-merge was automatically disabled November 11, 2026 03:30

Head branch was pushed to by a user without write access

@EmilienM
EmilienM force-pushed the fix/5184-exec-blocking-pool/EmilienM branch from 2065ac7 to a5a3934 Compare November 11, 2026 03:30
@EmilienM

Copy link
Copy Markdown
Contributor Author

Rebased onto main (a5a39344c), so the branch now carries #5223 and the k3s installer pin.

No conflicts, and both commits are byte-identical to before (range-diff clean). Clippy, fmt and the full openshell-sandbox suite (326 tests) pass on the rebased tree. That should clear the two conformance lanes that died in Ansible at "Download K3s installer" with the old get.k3s.io checksum.

The test:e2e label is still set, so the lanes should restart on this push. #5184 also has state:accepted now, so the acceptance gate is happy.

@FrostGod

Copy link
Copy Markdown
Collaborator

/ok to test a5a3934

@FrostGod
FrostGod enabled auto-merge November 11, 2026 16:09
@EmilienM

Copy link
Copy Markdown
Contributor Author

All the red lanes on the latest run are the Nov 11 Azure DNS outage, not the PR. The vm-e2e and podman-e2e jobs all died in the apt setup step ("Temporary failure resolving 'azure.archive.ubuntu.com'") before any test ran, tmachine hit the same inside its Dockerfile, and Trivy/Core E2E result are just a cancelled twin run plus the aggregate of those.

I can't re-run failed jobs myself without admin rights on the repo. Could you hit "Re-run failed jobs" on the Branch E2E Checks run for a5a3934? Everything else on that SHA is green.

@FrostGod
FrostGod added this pull request to the merge queue Nov 12, 2026
Merged via the queue into NVIDIA:main with commit 622a157 Nov 12, 2026
220 of 236 checks passed
@FrostGod
FrostGod deleted the fix/5184-exec-blocking-pool/EmilienM branch November 12, 2026 23:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:sandbox Sandbox runtime and isolation work test:e2e Requires end-to-end coverage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: exec pipes and PTY masters read through the shared blocking pool, and the disconnect path scans /proc on a runtime worker

3 participants