Repository navigation
Treat an unreaped zombie instance as exited on Linux [patch] - #223
Open
matt-edmondson wants to merge 3 commits into
Open
matt-edmondson wants to merge 3 commits into
matt-edmondson wants to merge 3 commits into
Conversation
An instance that has exited but whose parent has not reaped it keeps its /proc entry: HasExited is false, its name still reads back, and its empty memory map leaves no main module. Since #215 that null module falls back to the name check, which the zombie passes, so every later launch saw "already running". The legacy plain-PID path had the same blind spot. Read the state field of /proc/<pid>/stat and count Z or X as exited in the stored, name-fallback and legacy checks. The #214 cross-user fallback is unchanged: a live process in another user's session is in state S or R. Fixes #219 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017vfJW3W1DsZmnUsfmWXu2k
CI's ubuntu-latest image now ships uutils coreutils, a multi-call binary that picks the utility from its executable name. HelperProcess copied /bin/sleep under a test name, so the copy printed "coreutils: unknown program 'StartTimeHelper'" and exited at once. Each helper test then raced an exited process: one failed with "process has exited" and the rest could pass without checking anything. On Linux the helper is now a copy of /bin/sh that runs `sleep 120` as a child. The copy keeps the requested name and main module and stays alive. ZombieProcess's short-lived child is a copied sh running `exit 0` for the same reason. Windows and macOS are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017vfJW3W1DsZmnUsfmWXu2k
The helper is now a shell waiting on a sleep child, and Kill() stopped only the shell, leaving its sleep running for up to two minutes after each test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017vfJW3W1DsZmnUsfmWXu2k
|
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.



Fixes #219
What was wrong
On Linux, an instance that has exited but whose parent hasn't reaped it yet (a launcher, wrapper or supervisor that doesn't
wait()) keeps its/proc/<pid>entry in stateZ. For that process:HasExitedisfalseSince #215, a null main module falls back to the name check. The zombie passes that check, so every later launch reported "already running" until the parent went away. The legacy plain-PID path had the same blind spot.
Change
HasExited(Process)returnsprocess.HasExited || IsLinuxZombie(pid). It's used inIsStoredProcessRunning,IsStoredProcessRunningByNameandIsLegacyProcessRunning.IsLinuxZombiereads/proc/<pid>/statand treats stateZorXas exited. It does nothing on other platforms. When the state can't be read, it doesn't change the result.ParseLinuxProcessStatetakes the state field from after the last), because the command name in field 2 can itself contain spaces and parentheses.S/R, so it still falls back to the name and start-time check. The existingIsStoredProcessRunning_WhenMainModuleIsUnreadable_*tests still pass.Test helpers and the CI runner image
The first CI run failed in an existing test,
IsStoredProcessRunning_WhenMainModuleIsUnreadable_ShouldFallBackToNameAndStartTime, with "process has exited".ubuntu-latestnow ships uutils coreutils, a multi-call binary that picks the utility from its executable name.HelperProcesscopied/bin/sleepunder a test name, so the copy printedcoreutils: unknown program 'StartTimeHelper'and exited at once. Every helper-based test then raced an exited process./bin/shthat runssleep 120as a child. The copy keeps the requested name and main module. Windows and macOS are unchanged.sleepoutlives a test.Tests
IsAlreadyRunning_WhenStoredProcessIsAnUnreapedZombie_ShouldReturnFalseandIsAlreadyRunning_WithLegacyPidFile_WhenProcessIsAnUnreapedZombie_ShouldReturnFalsecreate a real zombie. A shell backgrounds a copiedsh -c 'exit 0'that exits at once, thenexecs intosleep, which never reaps it. Disposing kills that parent, so init reaps the zombie. Each test writes the zombie into a JSON or legacy PID file and assertsIsAlreadyRunning()is false. On other platforms they are inconclusive.ParseLinuxProcessStatedata-row tests cover a command name likea) b (c)and input that can't be parsed.IsLinuxZombietaken out ofHasExited, both zombie tests fail and the other 65 pass.🤖 Generated with Claude Code
https://claude.ai/code/session_017vfJW3W1DsZmnUsfmWXu2k