`connect()` blocks for ~21 seconds on Windows when server is unreachable (SYN dropped)
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 2/5
- Tempo stimato
- 1-3 ore
- Idoneità per principianti
- 78/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- c
- Ambito
- networking, operating-systems
Direzione di ricerca
Inizia da src/bsd.c, quindi segui bsd_create_socket e bsd_create_connect_socket per confermare quando viene eseguita connect() rispetto alla configurazione della modalità non bloccante. Convalida su Windows 10 o 11 collegandoti a un server irraggiungibile e verificando che il ciclo degli eventi rimanga reattivo; il comportamento su Linux deve rimanere invariato.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Problem Background
When a TCP client attempts to connect to a server that is unreachable (e.g., firewall silently drops SYN packets, or server is down and no RST is returned), the connect() call on a blocking socket on Windows blocks for approximately 21 seconds (the default TCP SYN retransmission timeout: 3s + 6s + 12s exponential backoff).
In uSockets with the libuv backend (LIBUS_USE_LIBUV), this causes the entire event loop to freeze for 21 seconds, making the application completely unresponsive. No timers fire, no other I/O is processed, and no callbacks are invoked during this period.
Root Cause
The issue is in the interaction between bsd_create_socket, bsd_set_nonblocking, and the call order of connect() on Windows.
In the original code, bsd_create_socket creates a socket and then calls bsd_set_nonblocking:
// src/bsd.c
LIBUS_SOCKET_DESCRIPTOR bsd_create_socket(int domain, int type, int protocol) {
int flags = 0;
#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK)
flags = SOCK_CLOEXEC | SOCK_NONBLOCK;
#endif
LIBUS_SOCKET_DESCRIPTOR created_fd = socket(domain, type | flags, protocol);
return bsd_set_nonblocking(apple_no_sigpipe(created_fd));
}
On Linux, SOCK_NONBLOCK is defined, so the socket is created as non-blocking directly in the socket() syscall. The bsd_set_nonblocking call is a safety net.
On Windows, however:
SOCK_NONBLOCKis not defined — thesocket()call creates a blocking socket.- The original
bsd_set_nonblockingon Windows was either missing or did not actually set the socket to non-blocking mode beforeconnect()was called. - The original code implicitly relied on libuv's
uv_poll_init_socket()to set the socket to non-blocking mode. However,uv_poll_init_socket()is called afterconnect()has already been invoked on a blocking socket. - When
connect()is called on a blocking socket and the server is unreachable (SYN silently dropped by a firewall), Windows blocks insideconnect()for the full TCP SYN timeout period (~21 seconds).
Call sequence (before fix):
bsd_create_socket()
→ socket() // creates BLOCKING socket on Windows (no SOCK_NONBLOCK)
→ bsd_set_nonblocking() // either missing or ineffective on Windows
→ returns fd
bsd_create_connect_socket()
→ bsd_create_socket()
→ connect(fd, ...) // BLOCKS for ~21 seconds on Windows when SYN is dropped!
// Event loop is completely frozen during this time
→ returns fd
us_socket_context_connect()
→ bsd_create_connect_socket()
→ us_poll_init() // registers with libuv
→ us_poll_start() // uv_poll_start() → uv_poll_init_socket() sets non-blocking
// BUT TOO LATE — connect() has already blocked for 21s
Wireshark Evidence
When the server is unreachable and the firewall drops SYN packets, the Windows TCP stack retransmits SYN at the following intervals:
T+0.0s [SYN] (initial SYN)
T+3.0s [SYN] (1st retransmit)
T+9.0s [SYN] (2nd retransmit)
T+21.0s connect() finally returns with WSAETIMEDOUT
During this entire 21-second period, the uSockets event loop is completely blocked — no timers, no I/O, no callbacks can be processed.
Impact
- Application becomes completely unresponsive for ~21 seconds when connecting to an unreachable server on Windows.
- Connection retry logic (e.g., 5-second reconnect timers) is completely blocked — they can only fire after the 21-second blocking
connect()returns. - The problem is Windows-specific because:
- Linux uses
SOCK_NONBLOCKat socket creation time. - macOS/BSD also support
SOCK_NONBLOCK. - Windows has no equivalent flag for
socket().
- Linux uses
Solution
Set the socket to non-blocking mode immediately after creation, before connect() is called, using ioctlsocket(fd, FIONBIO, &nonblocking) on Windows.
Patch
In src/bsd.c, fix bsd_set_nonblocking to actually set non-blocking mode on Windows:
LIBUS_SOCKET_DESCRIPTOR bsd_set_nonblocking(LIBUS_SOCKET_DESCRIPTOR fd) {
#ifdef _WIN32
- /* Empty or ineffective implementation */
- /* Previously relied on libuv's uv_poll_init_socket to set non-blocking,
- * but that happens AFTER connect() has already blocked for 21 seconds. */
+ /* WARNING: Libuv's uv_poll_init_socket does set the socket to non-blocking,
+ * but only AFTER connect() has already been called on a blocking socket.
+ * On Windows, connect() on a blocking socket blocks for ~21 seconds when
+ * the server is unreachable (firewall drops SYN), freezing the entire
+ * event loop. We must set non-blocking BEFORE connect() is called. */
+ unsigned long nonblocking = 1;
+ ioctlsocket(fd, FIONBIO, &nonblocking);
#else
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
#endif
How It Works
ioctlsocket(fd, FIONBIO, &nonblocking)is the Windows API to set a socket to non-blocking mode (equivalent tofcntl(..., O_NONBLOCK)on POSIX systems).- The call is placed in
bsd_set_nonblocking, which is called frombsd_create_socketbeforebsd_create_connect_socketcallsconnect(). - With a non-blocking socket,
connect()returns immediately withWSAEWOULDBLOCK(equivalent toEINPROGRESSon POSIX). The actual connection happens asynchronously. - libuv monitors the socket for writability and reports completion (or error) via the event loop — without blocking.
Call sequence (after fix):
bsd_create_socket()
→ socket() // creates BLOCKING socket on Windows
→ bsd_set_nonblocking() // NOW sets non-blocking via ioctlsocket(FIONBIO) ← FIX
→ returns NON-BLOCKING fd
bsd_create_connect_socket()
→ bsd_create_socket()
→ connect(fd, ...) // returns immediately with WSAEWOULDBLOCK ← NO MORE 21s BLOCK
→ returns fd
us_socket_context_connect()
→ bsd_create_connect_socket()
→ us_poll_init()
→ us_poll_start() // libuv monitors socket asynchronously
// Event loop continues running — timers, I/O all work
Testing
Tested on Windows 10/11:
- Before fix: Connecting to an unreachable server (SYN dropped by firewall) blocks
connect()for ~21 seconds. Event loop is frozen. No timers fire. Application appears hung. - After fix:
connect()returns immediately (WSAEWOULDBLOCK). Event loop continues running. Connection result (success or timeout) is reported asynchronously via theon_open/on_connect_errorcallback.
Also verified on Linux: Behavior is unchanged — SOCK_NONBLOCK at socket creation already ensures non-blocking behavior, and fcntl(F_SETFL, O_NONBLOCK) in bsd_set_nonblocking provides the safety net.
- Lingua principale
- C
- Stelle
- 1.5k
- Fork
- 310
- Merge medio
- 1h 12m
- PR unite (30g)
- 1
Preparare l'ambiente
Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di uNetworking/uSockets
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
uNetworking/uSockets#258 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
uNetworking/uSockets#256 · 1 reazione ·
-
enhancement help wanted
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
uNetworking/uSockets#252 · 5 commenti ·
-
enhancement
Difficoltà 4/5 3-5 giorni Idoneità per principianti 55/100
uNetworking/uSockets#263 ·
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 45/100
uNetworking/uSockets#255 · 2 commenti · 1 reazione ·
Tutte le issue di uNetworking/uSockets
Issue simili
-
Add `CPC_TAKE_TILL_CHARS`Apertadifficulty: easy
Difficoltà 2/5 1-3 ore Idoneità per principianti 64/100
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
-
performance solver
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
ChefKissInc/Inferno#319 ·
-
llvm-tools
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 72/100
llvm/llvm-project#235522 ·
I maintainer di solito rispondono entro 1 giorno