Don't let one namespace delete another's server records - #232
Open
subwire wants to merge 2 commits into
Open
Conversation
A registry directory can be shared by processes that cannot see each
other's servers. The easy way to arrive there is several containers
bind-mounting the same home directory, which is how agent harnesses tend
to run: the registry is shared, but sockets live in each container's own
/tmp and PIDs are per-namespace.
Every liveness test then fails on a sibling's record -- socket missing,
pid absent -- so pruning deletes the record of a server that is alive and
well. The owning container subsequently cannot find its own server:
`decompiler list` returns nothing while the process is running and
serving, and `load` blocks until its --timeout expires waiting for a
server that started successfully seconds earlier.
Traced across two concurrent containers:
container A: REGISTER 6fe01da235 (pid 1301)
container B: PRUNE 6fe01da235 socket_exists=False pid_alive=False
container B: REGISTER 89b45700ed (pid 1358)
container A: PRUNE 89b45700ed socket_exists=False pid_alive=False
On a 40-binary benchmark this cost 22 of 40 solves a phantom
"timed out waiting for server to start", 50 occurrences in one run.
Records now carry the namespace that created them, which makes "not
mine" distinguishable from "dead". Foreign records are skipped entirely:
never returned as ours, never deleted. Records without the field are
treated as local, so existing registries keep working exactly as before.
Deployments should still give each container its own registry; this makes
the shared case degrade into "cannot see it" rather than "deleted it".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both existing checks are proxies for the question actually being asked. A socket file survives an unclean death, so its presence proves only that a server once bound there. A PID can be recycled by an unrelated process. Either can report "alive" about a server that is gone -- and, across a shared registry, "dead" about one that is fine. ida-pro-mcp settles the same question by connecting to the endpoint (`probe_instance`), for the same reasons; its comment cites PID reuse and "process alive but the server crashed". Do likewise: after the two cheap checks pass, connect to the socket. Verified against a real backend: a live server is still reported and usable; after SIGKILL the socket file remains on disk and the server is correctly reported gone, where the file check alone would have said it was alive. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
The bug
A registry directory can be shared by processes that cannot see each other's servers. The easy way to get there is several containers bind-mounting the same home directory — which is how agent harnesses tend to run. The registry is shared; sockets live in each container's own
/tmp, and PIDs are per-namespace.So every liveness test fails on a sibling's record — socket missing, pid absent — and pruning deletes the record of a server that is alive and well.
The owner then cannot find its own server:
decompiler listreturns nothing while the process is running and serving, andloadblocks until--timeoutexpires waiting for a server that started successfully seconds earlier.Traced
Instrumenting every registry mutation across two concurrent containers:
They annihilate each other on every load.
Cost
On a 40-binary benchmark, 22 of 40 solves hit a phantom
Timed out waiting … for server to start— 50 occurrences in a single run — each burning the full timeout (up to 300s) while the server sat healthy. Reproduced directly with two containers sharing one home:registered 0 server(s)…0 visible, 1 process alive1 visible, 1 process aliveChange
Records carry the namespace that created them (hostname + pid/mnt namespace links), which makes "not mine" distinguishable from "dead". Foreign records are skipped entirely — never returned as ours, never deleted — so a container is the only writer of its own entries.
Records without the field are treated as local, so existing registries prune exactly as before.
This is defense in depth, not a licence to share: deployments should still give each container its own registry (we set
XDG_STATE_HOMEper container). It makes the shared case degrade into "I can't see it" instead of "I deleted it".Tests
Four: foreign records are never returned nor deleted; our own dead records are still pruned; new records carry a namespace; legacy records without one still prune.
🤖 Generated with Claude Code