Fix concurrent isolated-startup races (network create + ingress DNS latch) - #6071
Merged
Conversation
Concurrently starting workloads share the "toolhive-external" Docker network, and createNetwork checks for existence and then creates in two non-atomic steps. When several `thv run` supervisors start at once (e.g. multiple workloads in a group), more than one passes the existence check and races on NetworkCreate. Docker rejects the duplicate name with a conflict error, which propagated up as "failed to create external networks" and left every losing workload in status "error". Treat a conflict from NetworkCreate as success: a lost race means the network already exists, which is exactly the desired state. The new real-daemon test fires eight concurrent createNetwork calls for the same name and asserts every caller succeeds with a single network created; it fails against the old code and passes with the fix (skips without Docker). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ChrisJBurns
requested review from
JAORMX,
amirejaz,
blkt,
jhrozek and
rdimitrov
as code owners
July 27, 2026 23:44
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #6071 +/- ##
==========================================
- Coverage 72.20% 72.18% -0.03%
==========================================
Files 721 721
Lines 75062 75085 +23
==========================================
+ Hits 54198 54199 +1
- Misses 17003 17021 +18
- Partials 3861 3865 +4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
After the ingress port collision (#6069) is fixed, concurrent isolated startup still leaves a workload stuck in status "starting" behind a persistent ingress 502, even though the MCP server is healthy and listening. The ingress Squid cache_peer targets the MCP container by name; under concurrent startup the container's Docker DNS record can lag, Squid caches the failed lookup, and the peer never revives within the 2-minute readiness window. Ordering ingress creation after the MCP container (the existing mitigation) is not enough when DNS registration itself races. Resolve the MCP container's IP on the internal network and give it to the ingress proxy as the upstream address, so the cache_peer has no DNS lookup to latch on. A bad IP just yields connection-refused, which Squid retries and recovers from within seconds. The Envoy backend's ingress cluster is pointed at the same IP for parity. UpstreamHost falls back to the workload name when unset, so non-isolated paths are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jerm-dro
approved these changes
Jul 28, 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.
Summary
The
E2E Tests Core (core)shard has been failing on almost every PR because several MCP workloads of the same image start concurrently (thegroup_rm/list_groupspecs) and hit a family of concurrent isolated-startup races. Diagnostics added in #6063 captured the actual Docker errors. There are three distinct bugs; #6069 fixed one, and this PR fixes the other two:Shared-network create race (fixed here). Concurrently starting workloads share the
toolhive-externalnetwork.createNetworkchecks existence then creates in two non-atomic steps, so more than one caller races onNetworkCreateand all but one fails withnetwork ... already exists→ workloadstatus "error". Fix: treat aNetworkCreateconflict as success (the network is already in the desired state).Ingress port collision — fixed by Stop deriving the ingress proxy port from a fixed upstream port #6069 (
FindOrUsePort(upstreamPort+1)→ random), merged into this branch.Ingress DNS latch (fixed here). After the port collision is fixed, a workload still gets stuck in
status "starting"behind a persistent ingress502, even though the MCP server is healthy and listening. The ingress Squidcache_peertargets the MCP container by name; under concurrent startup the container's Docker DNS record lags, Squid caches the failed lookup, and the peer never revives within the 2-minute readiness window. Ordering ingress creation after the MCP container (the existing mitigation) is not enough when DNS registration itself races. Fix: resolve the MCP container's IP and give it to the ingress proxy as the upstream address, so there is no DNS lookup to latch on — a transient failure becomes connection-refused, which Squid retries and recovers from in seconds. The Envoy backend's ingress cluster is pointed at the same IP for parity.Together with #6069, this closes out all three concurrent-startup failure modes.
How this was found
Root-caused by reproducing each race directly: the network-create conflict fires 25/25 rounds under 8-way contention at the Docker API level (0/25 with the fix); the DNS latch was reproduced deterministically against a real Squid (upstream healthy + direct
curl200, yet Squid serves502 HIER_NONEfor 80s+), and a CI stress harness on the runner confirmed thestatus "error"(create/port) andstatus "starting"(DNS latch) signatures separately.Type of change
Test plan
task test) — new real-daemon concurrency test for the network-create race (fails without the fix);pkg/container/dockerpackage green including updated ingress-config and deploy tests asserting the ingress targets the MCP IPDoes this introduce a user-facing change?
No. Concurrent workload startup that previously failed intermittently (
failed to create external networks, or a workload stuckstarting/error) now succeeds.Generated with Claude Code