fix(engine): allocate distributed init ports outside the ephemeral range - #518
Open
CAICAIIs wants to merge 5 commits into
Open
fix(engine): allocate distributed init ports outside the ephemeral range#518CAICAIIs wants to merge 5 commits into
CAICAIIs wants to merge 5 commits into
Conversation
Collaborator
|
Hi @CAICAIIs , your change mitigates the observed collision but does not eliminate the bind-close-bind TOCTOU race, especially across processes. A stronger approach is to let the coordinator create and retain a TCPStore with port=0, then pass its resolved port to worker-side client stores. Also, the exclusion check/add should currently be under the same lock, and the fallback reintroduces the original ephemeral-port issue. |
CAICAIIs
added a commit
to CAICAIIs/AReno
that referenced
this pull request
Aug 27, 2026
Addresses maintainer review (inclusionAI#518): instead of probing a port with bind-close-bind (which raced with outbound traffic and could hand the same port to the train and rollout clusters), the coordinator now creates and retains a TCPStore with port=0 before spawning workers, and every worker joins the group as a TCPStore client on the resolved port. - protocol.py: _create_rendezvous_store() binds port=0 and is retained on the cluster for its lifetime; Cluster.start and start_partitioned_clusters resolve the store port into the shared world_spec before spawning - context.py: init_process_group joins via a client store and init_process_group(store=...), so no worker ever binds the rendezvous port - backend.py: rollout world_spec uses a placeholder port that start_partitioned_clusters fills in - tests: the gloo broadcast test mirrors production with a coordinator store; a new test asserts the resolved port is genuinely held (re-bind fails while the store is alive) and serves client stores find_free_port is removed; no public API change.
find_free_port() probed bind(0) and closed the reservation immediately; the just-freed ephemeral port could be re-taken as the source port of an outbound connection, leaving it in TIME-WAIT so the later TCPStore server bind failed with EADDRINUSE on busy hosts, and consecutive calls could return the same port for the train and rollout clusters. Ports are now probed with a real bind from a fixed non-ephemeral range (which also excludes listeners and TIME-WAIT sockets), kept distinct per process, with a bind(0) fallback. Fixes inclusionAI#517
Self-review fixes: - randint could return the inclusive upper bound 30000 while the test asserts port < 30000; make the range half-open [20000, 30000) - guard the process-local exclusion set with a lock so concurrent callers cannot hand out the same port - document the residual caveat for hosts that widen ip_local_port_range
Addresses maintainer review (inclusionAI#518): instead of probing a port with bind-close-bind (which raced with outbound traffic and could hand the same port to the train and rollout clusters), the coordinator now creates and retains a TCPStore with port=0 before spawning workers, and every worker joins the group as a TCPStore client on the resolved port. - protocol.py: _create_rendezvous_store() binds port=0 and is retained on the cluster for its lifetime; Cluster.start and start_partitioned_clusters resolve the store port into the shared world_spec before spawning - context.py: init_process_group joins via a client store and init_process_group(store=...), so no worker ever binds the rendezvous port - backend.py: rollout world_spec uses a placeholder port that start_partitioned_clusters fills in - tests: the gloo broadcast test mirrors production with a coordinator store; a new test asserts the resolved port is genuinely held (re-bind fails while the store is alive) and serves client stores find_free_port is removed; no public API change.
CI caught that test_policy_tensor_sync_cpu.py still imported the removed find_free_port; both real-gloo reshuffle tests now create a coordinator-held rendezvous store and pass its resolved port to the spawned workers, mirroring the production rendezvous path.
The store-based rendezvous now creates a real client TCPStore before calling init_process_group; the two mocked tests patched only init_process_group, so the client store tried to connect to the placeholder port 12345 (no server) and blocked for the connection timeout. Patch TCPStore alongside init_process_group so the tests keep exercising group construction only.
CAICAIIs
force-pushed
the
perf/fix-find-free-port
branch
from
August 27, 2026 06:30
6f70417 to
9ce7fed
Compare
Contributor
Author
|
Thanks @xsuler — reworked per your suggestion: the coordinator now creates and retains the TCPStore(port=0) and workers join as client stores (find_free_port removed, fallback gone); the PR is rebased onto the latest main and the conflict is resolved. |
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.
What does this PR do?
Fixes #517: distributed worker startup could fail with
EADDRINUSEon busyhosts.
find_free_port()probed a port withbind(0)and immediately closedthe reservation socket; the just-freed ephemeral port could be re-taken as
the source port of an outbound connection (leaving it in TIME-WAIT, so the
later
TCPStorebind failed), and consecutive calls could hand the same portto the train and rollout clusters.
New approach (per maintainer review): the coordinator creates and retains
the rendezvous store. Instead of probing a port, the coordinator now creates
a
TCPStore(port=0)and keeps it open for the cluster lifetime, so theresolved port is genuinely reserved before any worker starts. Workers join as
TCPStoreclients and initialize the process group withstore=, so noworker ever binds the rendezvous port — the bind-close-bind TOCTOU race is
eliminated, including across processes.
Changes:
areno/engine/protocol.py: new_create_rendezvous_store()binds port=0and is retained on the cluster;
Cluster.startandstart_partitioned_clustersresolve the store port into the sharedworld_specbefore spawning workers.find_free_portis removed.areno/engine/parallel/context.py:init_process_groupcreates a clientTCPStoreand passes it todist.init_process_group(store=...).areno/api/backend/cuda/backend.py: the rollout world_spec uses aplaceholder port that
start_partitioned_clustersfills in.store; a new test asserts the resolved port is genuinely held (re-binding
the same port fails while the store is alive) and serves client stores.
No public API or CLI change.
Repro evidence
On a shared 8×A100 node, 4 consecutive
areno trainstartups failed withDistNetworkError: ... EADDRINUSEon freshly "free" ports (39625 / 34127 /58051 / 49989);
ssat failure time showed the chosen port in TIME-WAIT asthe source of an outbound connection. With the coordinator-held store,
startups on the same busy host succeed reliably.
Test commands (CPU)
The gloo broadcast test spawns 4 worker processes that join through the
coordinator-held store (client stores +
init_process_group(store=...)),exercising the production rendezvous path end to end.
Type of change
Checklist
pytest tests/ -k cpu).