Bug
find_free_port() (areno/engine/protocol.py) hands out ports that can be
re-taken by outbound traffic between the check and the TCPStore bind, so
distributed worker startup fails on busy hosts with a spurious
DistNetworkError: ... EADDRINUSE.
Root cause
def find_free_port() -> int:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.bind(("127.0.0.1", 0)) # OS picks lowest free ephemeral port
return int(sock.getsockname()[1]) # ... then the socket is closed
bind(0) allocates from the kernel ephemeral range (32768–60999 on Linux) and
the reservation socket is closed immediately. Two things then race before the
worker's TCPStore server binds the same port:
- Consecutive calls return the same port.
bind(0) → close() →
bind(0) reuses the just-freed lowest ephemeral port, so the train and
rollout clusters can be handed the identical port in one startup (two
find_free_port() call sites: protocol.py cluster start and
backend.py rollout cluster).
- Outbound connections steal the freed port. Any new outbound connection
in the same process (model hub downloads, metrics, etc.) can take the
just-freed port as its source port; after that connection closes, the port
sits in TIME-WAIT, and a subsequent TCPStore bind fails with
EADDRINUSE (no SO_REUSEADDR).
Observed on a shared 8×A100 node: 4 consecutive areno train startups
failed with EADDRINUSE on ports 39625 / 34127 / 58051 / 49989, each
reported as freshly "free" by find_free_port. ss at failure time showed
the chosen port in TIME-WAIT as the source of an outbound connection
(e.g. 192.168.200.17:43171 → 192.168.200.17:15333).
Repro conditions
- Linux host with any outbound connection activity around worker startup
(model download, metrics, or other users' traffic on a shared box);
- or any run that creates more than one cluster (train + rollout partitions),
where two find_free_port() calls race for the same ephemeral port.
A local patch that makes find_free_port pick a random port outside the
ephemeral range (20000–30000), verify it is free with a bind probe, and keep
returned ports distinct within the process fixes both failure modes; all
subsequent training runs on the same busy host succeeded.
Suggested fix
- allocate from a fixed non-ephemeral range instead of
bind(0);
- keep a process-local set of already-returned ports;
- optionally retry the
TCPStore bind on EADDRINUSE as a belt-and-braces
fallback.
Happy to open a PR with the fix plus a CPU test that asserts consecutive calls
return distinct ports and that the chosen port is bindable.
Bug
find_free_port()(areno/engine/protocol.py) hands out ports that can bere-taken by outbound traffic between the check and the
TCPStorebind, sodistributed worker startup fails on busy hosts with a spurious
DistNetworkError: ... EADDRINUSE.Root cause
bind(0)allocates from the kernel ephemeral range (32768–60999 on Linux) andthe reservation socket is closed immediately. Two things then race before the
worker's
TCPStoreserver binds the same port:bind(0)→close()→bind(0)reuses the just-freed lowest ephemeral port, so the train androllout clusters can be handed the identical port in one startup (two
find_free_port()call sites:protocol.pycluster start andbackend.pyrollout cluster).in the same process (model hub downloads, metrics, etc.) can take the
just-freed port as its source port; after that connection closes, the port
sits in
TIME-WAIT, and a subsequentTCPStorebind fails withEADDRINUSE(noSO_REUSEADDR).Observed on a shared 8×A100 node: 4 consecutive
areno trainstartupsfailed with
EADDRINUSEon ports 39625 / 34127 / 58051 / 49989, eachreported as freshly "free" by
find_free_port.ssat failure time showedthe chosen port in
TIME-WAITas the source of an outbound connection(e.g.
192.168.200.17:43171 → 192.168.200.17:15333).Repro conditions
(model download, metrics, or other users' traffic on a shared box);
where two
find_free_port()calls race for the same ephemeral port.A local patch that makes
find_free_portpick a random port outside theephemeral range (20000–30000), verify it is free with a bind probe, and keep
returned ports distinct within the process fixes both failure modes; all
subsequent training runs on the same busy host succeeded.
Suggested fix
bind(0);TCPStorebind onEADDRINUSEas a belt-and-bracesfallback.
Happy to open a PR with the fix plus a CPU test that asserts consecutive calls
return distinct ports and that the chosen port is bindable.