Skip to content

Retry listener select() on EINTR instead of exiting the listener thread - #733

Closed
dmitriy-bty wants to merge 1 commit into
LibVNC:masterfrom
dmitriy-bty:fix/listener-eintr-retry
Closed

Retry listener select() on EINTR instead of exiting the listener thread#733
dmitriy-bty wants to merge 1 commit into
LibVNC:masterfrom
dmitriy-bty:fix/listener-eintr-retry

Conversation

@dmitriy-bty

Copy link
Copy Markdown

Summary

The background listener thread (listenerRun in src/libvncserver/main.c),
used when the server runs with rfbRunEventLoop(screen, usec, TRUE), exits
permanently the first time select() fails with EINTR. Once it exits, the
listening sockets stay open but nothing ever calls accept(), so new clients
complete the TCP handshake and then hang forever. This fixes #732.

Root cause

listenerRun treats any select() error as fatal:

if (select(screen->maxFd+1, &listen_fds, NULL, NULL, &tv) == -1) {
    rfbLogPerror("listenerRun: error in select");
    return THREAD_ROUTINE_RETURN_VALUE;   /* thread ends here, for good */
}

EINTR is not a real failure — it just means a signal was delivered while the
thread was blocked in select(). Per signal(7), select() is one of the
calls that is not automatically restarted by the kernel after a signal
handler runs, even when the handler is installed with SA_RESTART. So a single
stray signal landing on the listener thread makes select() return -1 with
errno == EINTR, the thread returns, and the accept loop is gone. The
listening file descriptors are never closed, so from a client's point of view
the port is open and connect() succeeds, but the connection is never accepted
and the viewer waits indefinitely.

Change

Guard the error path with an EINTR check: on an interrupted select(),
continue the loop and re-enter select() instead of logging an error and
returning. All other errno values keep the existing log-and-return behavior,
so genuine failures are still surfaced exactly as before.

if (select(screen->maxFd+1, &listen_fds, NULL, NULL, &tv) == -1) {
    if (errno == EINTR)
        /* A signal interrupted select(); this is benign ... retry. */
        continue;
    rfbLogPerror("listenerRun: error in select");
    return THREAD_ROUTINE_RETURN_VALUE;
}

This is the standard EINTR-retry idiom. errno.h is already included in this
file, and the change is scoped to the single reported site in
src/libvncserver/main.c.

Validation

  • Builds cleanly with the CMake build (cmake -S . -B build && cmake --build build --target vncserver), pthreads threading backend, no new warnings.
  • Runtime reproducer (RED → GREEN). A small harness starts a server with
    rfbRunEventLoop(screen, usec, TRUE), lets the listener thread reach
    select(), then delivers SIGUSR1 to that thread via pthread_kill() to
    force EINTR, and finally opens a TCP connection and waits for the RFB
    ProtocolVersion greeting the server sends on accept():
    • Before the fix: listenerRun: error in select: Interrupted system call is
      logged, the listener thread dies, no greeting arrives, the client read
      times out.
    • After the fix: the listener survives the interruption, accepts the client
      and sends RFB 003.008; the connection succeeds.

Related (not changed here)

The per-client processing loop in clientInput (same file) has the same shape —
select() returning < 0 breaks the loop, terminating that client's thread,
without an EINTR guard. That path only tears down one already-connected
client rather than the whole accept loop, so it is a separate, lower-severity
issue; I have kept this PR scoped to the listener thread reported in #732 and
am happy to follow up on the client loop separately if you'd like.

Closes #732

The background listener thread (listenerRun) treated any select() failure,
including EINTR, as fatal: it logged an error and returned, terminating the
thread permanently. select() is not restarted by the kernel after a signal
(even with SA_RESTART, see signal(7)), so a single stray signal delivered to
the listener thread killed the accept loop while the listening sockets stayed
open. New clients then completed the TCP handshake but were never accept()ed
and hung forever.

Retry the loop when errno == EINTR; preserve the existing log-and-return
behavior for all other select() errors.

Closes LibVNC#732
@bk138

bk138 commented Jul 6, 2026

Copy link
Copy Markdown
Member

#734 was more complete, merged that one.

@bk138 bk138 closed this Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Background listener thread exits permanently when select() fails with EINTR - server keeps listening but never accepts again

2 participants