Repository navigation
fix(ci): reap leaked Activity gate servers and wait for a quiet host - #2119
Merged
Merged
Conversation
The WebUI Activity gate leaked its server when the verifier died or its cleanup gave up, and the leak failed every later run of the job at the GPU precondition, which printed only a count. The gate also scored while another compile ran on the runner and reported degradations inside its own noise. - scripts/webui/activity_gate_host.py precheck runs first in the gate body: it stops a process an earlier run leaked (executable in the installed-artifact directory or working in a verifier run directory, and orphaned or with its directory deleted), waits up to 600 s for the mean runnable-task count from /proc/loadavg to fall to a quarter of the CPUs, then fails closed naming each remaining GPU compute process. It writes the host state it saw for the evidence. - activity_gate_host.py reap runs as an always() step after the gate: it stops what the verifier's pid file names, checking start times so a reused pid is never signalled, plus anything else of the job's identity, and fails the job if anything survives. - verify_activity_performance.py records every owned process in --pid-file, embeds --host-state into the full evidence with an after-activity load sample, and terminate_owned no longer raises from its SIGKILL wait and now stops group members a quickly exiting leader left behind. - Tests for both failure paths in terminate_owned (each fails on the previous code), the host helper with real processes, and summary compatibility of the new evidence field. Closes #1949
Review follow-ups for the Activity gate host guards. - Processes with a controlling terminal are never signalled, and a process matched only by its working directory or HOME is signalled alone, never with its group, so a developer's shell sitting in a run directory cannot take its editor or pager down with it. - The browser Playwright launches leads a process group of its own; it, node and the server now match by HOME=<run directory>/home. The verifier records the node activity script in the pid file and starts Xvfb and openbox in the run directory. - Signals go through a pidfd checked against the recorded start time; a reused pid ends the recorded group instead of being signalled. Pid-file records carry the boot id, and an unreadable file or record no longer stops the identity sweep (it still fails the step). - GPU processes that are not the job's are named without executable or directory paths, and host snapshots list only compiler names and pids, since the logs and artifacts are public. - The precheck checks the GPU before and after the quiet wait, records host snapshots at start and end, and writes the host state atomically. nvidia-smi timeouts are refusals. - The after-activity load sample is taken before the output is validated, so a noisy verdict keeps it; terminate_owned records a PermissionError instead of raising it. - Job timeout 90 to 120 minutes to fit two cold builds plus the 75 minute gate step; the reap step skips when the checkout never happened.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The
WebUI installed artifactjob's Activity performance gate had two environmental faults on the GB10 runner (#1949). It leaked its server when the verifier was killed or its cleanup gave up, and the leaked server then failed every later run at the GPU precondition, which printed only a count (on 2026-10-05, five consecutive runs of PR #2117 aftermain680f64e failed in scoring). It also scored while other work ran on the host and reported degradations inside its own noise. This PR closes both in the job itself.Changes
scripts/webui/activity_gate_host.py precheckreplaces the barenvidia-smicount as the first command of the gate body (after the CI flock andgpu-lock):$RUNNER_TEMP/mlxcel-webui-installed/, or its working directory orHOMEis a verifier run directory ($RUNNER_TEMP/mlxcel-activity-performance-*). The verifier gives the server, node and the browserHOME=<run dir>/home; Playwright launches the browserdetached, in a process group of its own, soHOMEis what reaches it.(deleted)or its run directory gone. A live process of that identity, or a leak owned by another uid, fails closed instead./proc/loadavgover 10 one-second samples is at most a quarter of the CPUs (5 on GB10), and fails closed with a host snapshot otherwise. Load averages andps %cpulag a fresh compile, so neither is used, and the window must fill before the gate can refuse.webui-activity-host-state.json, uploaded with the diagnostics.activity_gate_host.py reap, a newif: always()step after the gate: stops the process groups the verifier's pid file names and anything else of the job's identity, prints what it found, and fails the job if anything survives or the pid file is unreadable (the identity sweep still runs). A missing pid file is a clean no-op.HOMEis signalled alone, never with its group; signals go through a pidfd checked against the recorded start time, so a reused pid is never signalled; pid-file records carry the boot id.verify_activity_performance.py:--pid-filerecords every owned process (Xvfb, openbox, the server, the node activity script) outside the run directory as it starts; Xvfb and openbox now run in the run directory.--host-stateembeds the precheck's state into the full evidence ashost_state.gate_start, plus ahost_state.after_activityload sample taken before the output is validated, so a noisy verdict keeps it. The summary does not carry it andsummarize_activity_evidence.pyis unchanged.terminate_ownedno longer raises out of its SIGKILL wait or on aPermissionError, and it stops group members a quickly exiting leader left behind instead of only recordingprocess_group_empty: false.docs/webui-integration-matrix.md; the new tests joinmake verify-webui-helper-testsand the job's helper test step.The server spawns no child processes (model workers are in-process threads), so its process group is the whole server. The verdict rule and thresholds are untouched (#1925).
Changes during review
The security review found that a directory-only match could SIGKILL a developer's shell and its whole group, that node and the browser had no reap handle, and that paths of unrelated GPU processes would reach public logs. All three are fixed above, together with pidfd signalling, tolerant pid-file parsing, the GPU check before the quiet wait, and the after-activity sample on the failure path from the implementation review.
Verification
make verify-webui-helper-tests: all suites pass (18 host-helper tests, 21 verifier tests, 10 summary tests). The twoterminate_ownedtests fail on the previous code: one raisessubprocess.TimeoutExpiredfrom the SIGKILL wait, the other ends with the group still populated.actionlint1.7.7 is clean.lablup-dgxspark21(machine spark-101):(deleted)and PPID 1, stopped it with SIGTERM, and the gate then passed. This is the leak that failed PR fix(generate): sample each penalized step against the token just read #2117's five runs.server_shutdownexit_code 0,forced false,process_group_empty true, full evidence carrieshost_state.gate_startandafter_activity, and reap found nothing to do.host did not become quiet within 60s: mean runnable tasks 23.8 over the last 10 samples, threshold 5; with the load removed it passed at 1.1. The verifier was then SIGKILLed mid-activity while the server held 4604 MiB, and the always() reap stopped Xvfb, openbox, the server, node and the browser (the last byHOME); the following check foundnvidia-smi --query-compute-appsandpgrep -af mlxcel-server-webuiboth empty.mlxcel-serverserving qwen3-0.6b-4bit, undergpu-lock): a leaked server (PPID 1, deleted run directory, 4116 MiB) was named and stopped; a foreign GPU process was refused at once, named without its path, and left running; a busy host (another session's compile) was refused after the wait; a server left by a SIGKILLed verifier was reaped by pid file.Closes #1949