Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

`connect()` blocks for ~21 seconds on Windows when server is unreachable (SYN dropped)

Abierto Apto para principiantes
#259 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
2/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
78/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
c

Línea de trabajo

Comienza en src/bsd.c y luego sigue bsd_create_socket y bsd_create_connect_socket para confirmar cuándo se ejecuta connect() en relación con la configuración del modo no bloqueante. Valídalo en Windows 10 u 11 conectándote a un servidor inalcanzable y comprobando que el bucle de eventos siga respondiendo; el comportamiento en Linux debe permanecer sin cambios.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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:

  1. SOCK_NONBLOCK is not defined — the socket() call creates a blocking socket.
  2. The original bsd_set_nonblocking on Windows was either missing or did not actually set the socket to non-blocking mode before connect() was called.
  3. 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 after connect() has already been invoked on a blocking socket.
  4. When connect() is called on a blocking socket and the server is unreachable (SYN silently dropped by a firewall), Windows blocks inside connect() 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_NONBLOCK at socket creation time.
    • macOS/BSD also support SOCK_NONBLOCK.
    • Windows has no equivalent flag for socket().

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
  1. ioctlsocket(fd, FIONBIO, &nonblocking) is the Windows API to set a socket to non-blocking mode (equivalent to fcntl(..., O_NONBLOCK) on POSIX systems).
  2. The call is placed in bsd_set_nonblocking, which is called from bsd_create_socket before bsd_create_connect_socket calls connect().
  3. With a non-blocking socket, connect() returns immediately with WSAEWOULDBLOCK (equivalent to EINPROGRESS on POSIX). The actual connection happens asynchronously.
  4. 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 the on_open / on_connect_error callback.

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.

Lenguaje dominante
C
Estrellas
1.5k
Forks
307
Métricas de merge de PR
Sin PR fusionados en 30 d

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de uNetworking/uSockets

Todos los issues de uNetworking/uSockets

Issues similares

Más issues de C

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.