From 3ec27d115b0a6ff3ac20fe4486239150c3872e25 Mon Sep 17 00:00:00 2001 From: MiTHRAL Date: Fri, 28 Aug 2026 13:28:58 -0400 Subject: [PATCH] netcode-revival: receive timeout on the relay transport socket The Run thread blocked in recvfrom with no SO_RCVTIMEO, so when there was no inbound game traffic the 4s heartbeat never fired (the relay swept the silent transport client after ~8s) AND StopLocked()'s join() deadlocked SetRelay on a room change (hanging the ghost network loop). A 200ms receive timeout wakes the loop regularly: the heartbeat keeps the transport alive and shutdown/room-change joins complete promptly. --- src/system/xnet_transport.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/system/xnet_transport.cpp b/src/system/xnet_transport.cpp index 04727da..cc60f6f 100644 --- a/src/system/xnet_transport.cpp +++ b/src/system/xnet_transport.cpp @@ -60,6 +60,23 @@ void SocketClose(int64_t s) { #endif } +// The receive loop must wake regularly so the heartbeat fires even with no +// inbound traffic (otherwise the relay sweeps a silent transport client +// after ~8s) and so StopLocked()'s join completes promptly instead of +// deadlocking SetRelay on a room change. +void SocketSetRecvTimeout(int64_t s, int timeout_ms) { +#if REX_PLATFORM_WIN32 + DWORD tv = DWORD(timeout_ms); + ::setsockopt(SOCKET(s), SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&tv), sizeof(tv)); +#else + timeval tv = {}; + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + ::setsockopt(int(s), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); +#endif +} + bool SocketSendTo(int64_t s, const sockaddr_in* to, const void* buf, int size) { #if REX_PLATFORM_WIN32 const int n = ::sendto(SOCKET(s), reinterpret_cast(buf), size, 0, @@ -176,6 +193,8 @@ void XNetTransport::Reconnect() { REXLOG_WARN("xnet transport: cannot open relay socket"); return; } + // Wake the receive loop every 200ms (heartbeat + prompt shutdown). + SocketSetRecvTimeout(sock_, 200); REXLOG_INFO("xnet transport: connecting to relay '{}:{}' room '{}' (vip {}.{}.{}.{})", host_, port_, room_.c_str(), (LocalVirtualIp() >> 24) & 0xFF, (LocalVirtualIp() >> 16) & 0xFF, (LocalVirtualIp() >> 8) & 0xFF, LocalVirtualIp() & 0xFF);