Release v0.51.304 — Release JT (stage-p2a — un-held terminal reaper + opt-in Docker GPU) - #3757
Conversation
…3725, #2577) Embedded-terminal descendants reparented to the WebUI process could linger as zombies. The reaper now calls os.waitpid(-terminal_pgid, WNOHANG) scoped to the terminal's own process group (terminals spawn with start_new_session=True, so proc.pid == pgid) rather than process-wide waitpid(-1), which would otherwise reap unrelated WebUI subprocess children and silently coerce their exit codes to 0. Bounded by a 64-iteration limit and lock-guarded. Runs on reader cleanup and terminal close. Co-authored-by: rodboev <rodboev@users.noreply.github.com>
The default image stays CPU-only. A new INSTALL_GPU_LIBS=1 build arg installs VA-API user-space libraries for users passing through host GPU devices, and docker_init.bash preserves Docker --group-add supplemental groups (e.g. render/ video for /dev/dri) when dropping privileges to the runtime user. Default (INSTALL_GPU_LIBS=0) is a no-op. Docs + regression test included. Co-authored-by: rodboev <rodboev@users.noreply.github.com>
|
| Filename | Overview |
|---|---|
| api/terminal.py | Adds _reap_terminal_descendants() — scoped waitpid(-pgid, WNOHANG) replacing the old process-wide form — called from both _reader_loop and close_terminal; guarded by a global lock (unnecessarily broad but correct). |
| docker_init.bash | Adds a group-preservation loop that maps Docker --group-add GIDs to named groups and adds hermeswebui to each before privilege drop; contains one dead if [ -n "$group_name" ] guard after a continue. |
| Dockerfile | Introduces opt-in ARG INSTALL_GPU_LIBS=0 block; default path is a no-op, GPU libs only installed on =1; Intel non-free driver has graceful apt-cache show fallback. |
| tests/test_terminal_zombie_reaper.py | Linux-only tests covering the PGID-scoped reaper: fork/exec integration test, monkeypatched close_terminal flow, error-handling, and bounded-iteration tests — all logically sound. |
| tests/test_docker_gpu_runtime_docs.py | Snapshot tests asserting Dockerfile, docker_init.bash, docs, and CHANGELOG content; the changelog test anchors its upper bound on a hardcoded older version string that will raise ValueError if that entry is ever pruned. |
| docs/docker.md | Adds GPU runtime documentation covering Intel/AMD VA-API and NVIDIA passthrough; explicitly disclaims native verification and documents the supplemental-group preservation behavior. |
| CHANGELOG.md | Adds v0.51.304 entry documenting the PGID-scoped reaper fix and the opt-in GPU image path. |
Sequence Diagram
sequenceDiagram
participant RT as _reader_loop (thread)
participant CT as close_terminal (caller)
participant R as _reap_terminal_descendants
participant OS as os.waitpid
Note over RT,OS: Shell exits naturally
RT->>RT: proc.poll() → stores returncode
RT->>R: _reap_terminal_descendants(proc.pid)
R->>OS: waitpid(-pgid, WNOHANG) [scoped to terminal PGID]
OS-->>R: (child_pid, status) or (0,0)
R-->>RT: reaped count
Note over CT,OS: close_terminal called concurrently
CT->>CT: killpg(SIGHUP) + proc.wait(1.5s)
CT->>R: _reap_terminal_descendants(proc.pid)
Note over R: global lock serializes concurrent calls
R->>OS: waitpid(-pgid, WNOHANG)
OS-->>R: ECHILD / (0,0) — already reaped
R-->>CT: 0
Reviews (1): Last reviewed commit: "docs(changelog): stamp v0.51.304 — Relea..." | Re-trigger Greptile
| if [ -z "$group_name" ]; then | ||
| echo "!! WARNING: Could not create supplemental group for GID $gid; GPU device access may be unavailable" | ||
| continue | ||
| fi | ||
| if [ -n "$group_name" ]; then | ||
| usermod -a -G "$group_name" hermeswebui 2>/dev/null || echo "!! WARNING: Could not add hermeswebui to supplemental group $group_name ($gid)" | ||
| fi |
There was a problem hiding this comment.
The
if [ -n "$group_name" ] check on line 285 is unreachable dead code. The continue two lines above already guarantees that group_name is non-empty when execution reaches this point — any empty-group_name path has already bailed out. The guard adds noise and could mislead future readers into thinking there is a case where group_name is empty here.
| if [ -z "$group_name" ]; then | |
| echo "!! WARNING: Could not create supplemental group for GID $gid; GPU device access may be unavailable" | |
| continue | |
| fi | |
| if [ -n "$group_name" ]; then | |
| usermod -a -G "$group_name" hermeswebui 2>/dev/null || echo "!! WARNING: Could not add hermeswebui to supplemental group $group_name ($gid)" | |
| fi | |
| if [ -z "$group_name" ]; then | |
| echo "!! WARNING: Could not create supplemental group for GID $gid; GPU device access may be unavailable" | |
| continue | |
| fi | |
| usermod -a -G "$group_name" hermeswebui 2>/dev/null || echo "!! WARNING: Could not add hermeswebui to supplemental group $group_name ($gid)" |
| def test_changelog_mentions_optional_gpu_runtime_path(): | ||
| changelog = _repo_text("CHANGELOG.md") | ||
| unreleased = changelog[changelog.index("## [Unreleased]"):changelog.index("## [v0.51.293]")] |
There was a problem hiding this comment.
The test anchors the upper bound of the changelog slice on the literal string
"## [v0.51.293]". If changelog history is ever trimmed and that entry is removed, the test raises ValueError: substring not found rather than giving a useful assertion failure. A safer sentinel is the nearest preceding release entry.
| def test_changelog_mentions_optional_gpu_runtime_path(): | |
| changelog = _repo_text("CHANGELOG.md") | |
| unreleased = changelog[changelog.index("## [Unreleased]"):changelog.index("## [v0.51.293]")] | |
| def test_changelog_mentions_optional_gpu_runtime_path(): | |
| changelog = _repo_text("CHANGELOG.md") | |
| # Use the immediately preceding release as the lower-bound sentinel so the | |
| # slice stays valid even if older entries are pruned from the changelog. | |
| unreleased = changelog[changelog.index("## [Unreleased]"):changelog.index("## [v0.51.303]")] |
| _terminal_descendant_reaper_lock = threading.Lock() | ||
| _TERMINAL_DESCENDANT_REAPER_LIMIT = 64 |
There was a problem hiding this comment.
Global lock over-serializes independent PGIDs
_terminal_descendant_reaper_lock is process-wide, so two concurrent calls for different terminal sessions block each other even though waitpid(-pgid_A, …) and waitpid(-pgid_B, …) are completely independent at the kernel level. The lock is only required to prevent two callers from racing on the same terminal's PGID. In practice the impact is negligible (each call exits after one ECHILD in the common case), but a per-session lock stored on TerminalSession would be more precise if this code ever needs to scale to many concurrent terminal closings.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
… opt-in Docker GPU) (nesquena#3757) * fix(terminal): reap reparented terminal descendants by process group (nesquena#3725, nesquena#2577) Embedded-terminal descendants reparented to the WebUI process could linger as zombies. The reaper now calls os.waitpid(-terminal_pgid, WNOHANG) scoped to the terminal's own process group (terminals spawn with start_new_session=True, so proc.pid == pgid) rather than process-wide waitpid(-1), which would otherwise reap unrelated WebUI subprocess children and silently coerce their exit codes to 0. Bounded by a 64-iteration limit and lock-guarded. Runs on reader cleanup and terminal close. Co-authored-by: rodboev <rodboev@users.noreply.github.com> * docs(docker): add opt-in GPU runtime image path (nesquena#3721, nesquena#3243) The default image stays CPU-only. A new INSTALL_GPU_LIBS=1 build arg installs VA-API user-space libraries for users passing through host GPU devices, and docker_init.bash preserves Docker --group-add supplemental groups (e.g. render/ video for /dev/dri) when dropping privileges to the runtime user. Default (INSTALL_GPU_LIBS=0) is a no-op. Docs + regression test included. Co-authored-by: rodboev <rodboev@users.noreply.github.com> * docs(changelog): stamp v0.51.304 — Release JT (stage-p2a nesquena#3725 nesquena#3721) --------- Co-authored-by: nesquena-hermes <[email protected]> Co-authored-by: rodboev <rodboev@users.noreply.github.com>
Release v0.51.304 — Release JT (stage-p2a)
Un-held terminal-reaper fix + opt-in Docker GPU image. Both rebased onto fresh master (stale bases), fidelity-verified byte-identical to PR head.
Fixed
os.waitpid(-1, WNOHANG)), which could reap an unrelated WebUI subprocess child (git/update/provider) before its owner called.wait()and silently coerce its exit code to 0. Now scoped to the terminal's own process group (-terminal_pgid; terminals spawnstart_new_session=Trueso pid==pgid). Bounded + lock-guarded.Added
--build-arg INSTALL_GPU_LIBS=1(default 0 = no-op, CPU-only);docker_init.bashpreserves--group-addsupplemental device groups when dropping privileges.Gates
Closes #3725, closes #3721.