TCP transport: include rank information in send/recv timeout error messages
@amirafzali is already working on this.
Since Jun 18, 2026.
- Dominant language
- C++
- Stars
- 1.5k
- Forks
- 368
- Avg merge
- 19h 32m
- Merged PRs (30d)
- 3
Description
Summary
When a send/recv operation times out in the TCP transport, the resulting IoException doesn't say which ranks were involved. In large distributed jobs this makes it very hard to tell which peer is actually stuck. We should include the local rank and the remote rank(s) being waited on in the timeout message.
Current behavior
A recv timeout currently surfaces as:
[trainer22|6]:RuntimeError: [gloo/transport/tcp/unbound_buffer.cc:81] Timed out waiting 1800000ms for recv operation to complete
The [trainer22|6] prefix is added by the launcher (e.g. PyTorch / torchrun), not by Gloo, and the [gloo/.../unbound_buffer.cc:81] prefix comes from the GLOO_ERROR_MSG macro. Gloo's own message — Timed out waiting 1800000ms for recv operation to complete — carries no rank context. With thousands of ranks and only partial stderr, you can't tell which peer this rank was waiting on.
The two messages live in gloo/transport/tcp/unbound_buffer.cc:
Proposed behavior
Include the local rank and the remote rank(s) being waited on, e.g.:
Rank 6 timed out after 1800000ms waiting for recv from rank 22 (slot 7)
and for recv-from-any (multiple eligible sources):
Rank 6 timed out after 1800000ms waiting for recv from any of ranks [4, 5, 22] (slot 7)
(send is symmetric.) Even without the launcher's [host|rank] prefix, the message alone then identifies both ends of the stuck transfer.
Implementation sketch
All the data needed is already reachable from tcp::UnboundBuffer, so the change is self-contained to gloo/transport/tcp/unbound_buffer.{h,cc} and needs no cross-transport interface change.
Local rank — free. UnboundBuffer already holds a std::shared_ptr<tcp::Context> context_, and the base transport::Context stores const int rank / const int size. So context_->rank can be dropped into both messages directly, with no plumbing.
Remote rank — small plumb. The remote rank is known when the op is issued, but it isn't recorded on the buffer before the wait — recvRank_ / sendRank_ stay -1 until completion (they're only set in handleRecvCompletion / handleSendCompletion, which never fire on a timeout). Fix: record the target rank(s) on the buffer when the op starts:
UnboundBuffer::send(int dstRank, ...)→ stashdstRankUnboundBuffer::recv(int srcRank, ...)→ stashsrcRankUnboundBuffer::recv(std::vector<int> srcRanks, ...)→ stash the vector (recv-from-any has no single peer, so print the candidate set)
Then include the stashed rank(s), context_->rank, and the slot in the two timeout GLOO_ERROR_MSG(...) calls. The slot is especially useful for correlating the two sides of a hang.
This is consistent with the existing convention: waitRecv / waitSend already report the peer rank via the int* rank out-param on success — we'd just also surface it on the timeout path. There's already precedent for formatting ranks (Rank N, comma-joined peer lists) in tcp::Context::printConnectivityInfo().
Optional follow-ups
- Add a
getRemoteRank()accessor ontransport::Pair(symmetric with the existinggetLocalRank()/setLocalRank()); the TCPPairalready storesconst int rank_. That would let the address-based timeout/error messages inpair.cc— which today identify the peer only bypeer_.str()(anip:port, not a rank) — also include the rank. - Apply the same treatment to other transports (e.g.
ibverbs) for consistency.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.