Repository navigation
Testing: a store's port is held machine-wide; its container is acted on by id, never by name; another live process's container is refused (cross-process store race) - #45
Merged
Conversation
…er processes start EventStoreServers at one barrier instant and each must keep its own store (container by id, marker readable, unique port) for 20 s (card microplumberd-testing-store-port-race) On master: RED 7/10 — both chose 2701; the loser's create hit the name Conflict (4×), or its catch → Cleanup removed the winner's container BY NAME and both lost (3×, the winner's /health/live refused). StoreHolder is not packable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… lock file per port, FileShare.None → flock, released by the OS on death); its container is removed/stopped/restarted by the id it CREATED, never by name; a same-name container of another LIVE process is refused, named — never restarted (that wiped its in-memory store) or removed; a failed start's own failure is thrown, a cleanup failure rides along in Data (dev-clean: the 409 masked it) The race row: GREEN 10/10. Rows: a planted live-foreign-owner container of the same name → start refused, it is left stopped and present; dispose leaves it; a start failure whose cleanup also fails throws the start failure. The static readiness/removal seams are given the server, so a row touches only its own store (the readiness test counted every parallel class's stores before). Mutants: by-name Cleanup → the dispose row red; the reuse branch without the refusal → the start row red; the masking catch → the start-failure row red; the port lock non-exclusive (FileShare.ReadWrite) → the race row RED 4/10 (each a create Conflict; with id-removal and the refusal the loser fails alone, the winner survives). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 18, 2026
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.
Card
microplumberd-testing-store-port-race.The defect
Two test processes on one host (two engineers' suites) clobber each other's
EventStoreServers:PortSearcherstarts at 2701, and a free-port probe only sees a port as taken once its container has started, seconds later. So both processes chose the same port.eventstore-mem-<port>and were found by name. So the second process:Cleanupremoved the first's container by name ("connection refused"; Docker 409 "removal … already in progress" when both cleaned up, observed 2026-10-18 13:52Z by dev-clean); orA name over a shared namespace is a shared-resource operation.
Reproduction (red on master first)
MicroPlumberd.Testing.StoreHolderis a tiny console, not packable. Two copies start their stores at one barrier instant. For 20 s each asserts its own container (by id) runs and its own marker event stays readable.StorePortRaceTestsruns the pair and asserts both survived, on unique ports and in unique containers.Cleanupremoved the winner's container by name and both lost (the winner's/health/liverefused).The fix
PortLock: a lock file per port in a machine-wide directory (/tmp/microplumberd-testing-ports;CommonApplicationDataon Windows), openedFileShare.None(an advisoryflockon Unix). It is held for the store's lifetime and released by the OS if the process dies; another process skips a held port.Cleanup,StopandRestartact on that id only.Rows and mutants
FileShare.ReadWrite) → RED 4/10 (each a create Conflict; with by-id removal and the refusal, the loser fails alone and the winner survives)Cleanup→ redThe by-name removal cannot be reached by the race row, because the lock prevents the clash. That is why it has its own rows.
A first run of the lock mutant was invalid: my edit broke the build, and the runs used the stale fixed binary. It was discarded and re-run with a build gate; the numbers above are from the valid run.
Packaging
StoreHolderevaluatesIsPackable=false. The packable project set, evaluated on base and on this branch, is identical (27 = 27).Counts
Full
MicroPlumberd.Testsat 991d715: 196/202 (16 m 58 s). The 6 failures are exactly the long-standing set:GrpcApiTests×3;ProcessManagerSourceGenerationTests.CommandTypes;SubscriptionRunnerStateTests×2.No test-host crash; no containers left behind. Another repository's image smokes were running beside it as docker load.
Follow-up (rev-reach 9de418b9d, PR two):
1777/0666, andUnauthorizedAccessExceptiontreated as "next port");ToString(anAggregateException), not inData;🤖 Generated with Claude Code