From a1c9a718987fcb489c44ca4e101b071b5e72a700 Mon Sep 17 00:00:00 2001 From: arcattack Date: Wed, 22 Jul 2026 08:24:30 -0500 Subject: [PATCH] Join wait: non-blocking dial -- the spinner no longer stutters User-reported after the waiting-screen fix: "the animation keeps hanging." Cause: RelayDialTcp used a BLOCKING connect() -- against a down/unreachable operator each attempt froze the window thread for the OS SYN-retry window (seconds), so the spinner ran only during the 2s sleep between attempts. RelayDialTcp now connects NON-blocking and select()s in 100ms slices, pumping the message loop + repainting the waiting screen every slice (both in-flight and between retry attempts). The success path already returned a non-blocking socket (FIONBIO), so caller semantics are unchanged; the other dial sites (console dial, game-socket dial) inherit the smooth behavior. Verified: 4 window captures at 400ms intervals during a dead-host dial all differ in the spinner region (uniform diff energy == continuous animation) and IsHungAppWindow reads false on every sample. Co-Authored-By: Claude Opus 4.8 (1M context) --- engine/MUNGA_L4/L4NET.CPP | 74 +++++++++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/engine/MUNGA_L4/L4NET.CPP b/engine/MUNGA_L4/L4NET.CPP index eefad11..00384f8 100644 --- a/engine/MUNGA_L4/L4NET.CPP +++ b/engine/MUNGA_L4/L4NET.CPP @@ -1663,6 +1663,16 @@ SOCKET L4NetworkManager::RelayDialTcp( const SOCKADDR_IN &endpoint, int timeout_seconds) { + // + // NON-BLOCKING dial (2026-07-22, the "spinner keeps hanging" fix): the + // old blocking connect() froze the window thread for the OS SYN-retry + // window (seconds) per attempt -- the waiting screen stuttered. Connect + // non-blocking and select() in 100ms slices, pumping the message loop + + // repainting the waiting screen each slice (RelayWaitTick). The success + // path always returned a NON-blocking socket anyway (FIONBIO set below), + // so callers see identical semantics. + // + extern void BTWaitScreenPaint(const char *, const char *); unsigned long deadline = GetTickCount() + (unsigned long)timeout_seconds * 1000UL; for (;;) { @@ -1671,16 +1681,58 @@ SOCKET L4NetworkManager::RelayDialTcp( { return INVALID_SOCKET; } - if (connect(sock, (const sockaddr *)&endpoint, sizeof(endpoint)) == 0) + unsigned long enable = 1; + ioctlsocket(sock, FIONBIO, &enable); + int rc = connect(sock, (const sockaddr *)&endpoint, sizeof(endpoint)); + int wsa_error = (rc == 0) ? 0 : WSAGetLastError(); + int connected = (rc == 0); + if (!connected && wsa_error == WSAEWOULDBLOCK) + { + // in flight: watch writability (success) / except (refusal), + // animating between slices + for (;;) + { + fd_set write_set, except_set; + FD_ZERO(&write_set); FD_SET(sock, &write_set); + FD_ZERO(&except_set); FD_SET(sock, &except_set); + timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 100000; + int ready = select(0, NULL, &write_set, &except_set, &tv); + if (ready == 1 || ready == 2) + { + if (FD_ISSET(sock, &write_set)) + { + connected = 1; + } + else + { + wsa_error = WSAECONNREFUSED; + } + break; + } + if (GetTickCount() >= deadline) + { + wsa_error = WSAETIMEDOUT; + break; + } + MSG msg; + while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessageA(&msg); + } + BTWaitScreenPaint("WAITING FOR THE OPERATOR'S SESSION", + "connecting..."); + } + } + if (connected) { - unsigned long enable = 1; - ioctlsocket(sock, FIONBIO, &enable); BOOL noDelay = TRUE; setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&noDelay, sizeof(noDelay)); - return sock; + return sock; // already non-blocking (FIONBIO above) } - int wsa_error = WSAGetLastError(); closesocket(sock); if (GetTickCount() >= deadline || (wsa_error != WSAECONNREFUSED && wsa_error != WSAETIMEDOUT)) @@ -1689,7 +1741,17 @@ SOCKET L4NetworkManager::RelayDialTcp( << ")\n" << std::flush; return INVALID_SOCKET; } - Sleep(250); + { + MSG msg; + while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessageA(&msg); + } + BTWaitScreenPaint("WAITING FOR THE OPERATOR'S SESSION", + "connecting..."); + Sleep(100); + } } }