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) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-22 08:24:30 -05:00
co-authored by Claude Opus 4.8
parent f5e0ffcd14
commit a1c9a71898
+68 -6
View File
@@ -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);
}
}
}