Skip to content

Poll event queue treats EINTR as fatal, tearing down the socket pool #228

Description

@ianegordon

Summary

a196c2e fixed SocketPool<ePoll> to treat epoll_wait returning -1/EINTR as "no events" instead of a fatal error. The Poll backend still has the pre-fix behavior, and fixing it turns out to be non-trivial on Windows — hence an issue rather than a PR, to agree on direction first. (The kQueue backend has the same gap; a separate PR for that is in preparation.)

The POSIX bug

Poll.getNotifications() (FlyingSocks/Sources/SocketPool+Poll.swift):

let status = Socket.poll(&buffer, UInt32(buffer.count), interval.milliseconds)
guard status > -1 else {
    throw SocketError.makeFailed("poll.getNotifications poll")
}

poll(2) (Apple manpage, Linux equivalent): "[EINTR] A signal is delivered before the time limit expires and before any of the selected events occurs." No events are lost — the correct response is to poll again. Instead the throw unwinds SocketPool.run(), whose defer { cancelAll() } cancels every waiting continuation: a single signal delivery (e.g. a debugger pause/resume) tears down the whole pool for any embedder using .poll.

Why the obvious fix is wrong on Windows

Mirroring a196c2e (if status == -1 && errno == EINTR { return [] }) misbehaves under WinSDK:

  1. errno is shadowed. Socket+WinSock2.swift defines var errno: Int32 { WSAGetLastError() }, so the comparison would see Winsock codes (10000+), not CRT errno values.
  2. EINTR has no Windows alias. The WinSock2 shim deliberately aliases EWOULDBLOCK/EINPROGRESS/EISCONN to their WSAE* equivalents but defines no EINTR, so a bare errno == EINTR may not even compile on Windows.
  3. WSAEINTR means something different. Per Microsoft's Windows Sockets Error Codes, WSAEINTR (10004) is "Interrupted function call — a blocking operation was interrupted by a call to WSACancelBlockingCall" — an explicit cancellation, not a signal interruption — and the WSAPoll documentation does not list it as an expected result. Silently retrying it would be a behavior change, not a bug fix.

Proposed direction

Add the EINTR-retry branch under #if !canImport(WinSDK), matching a196c2e on POSIX platforms and leaving Windows behavior exactly as it is today:

guard status > -1 else {
    #if !canImport(WinSDK)
    // EINTR (signal) is not a failure: report no events so the caller
    // polls again rather than tearing down the server. See poll(2).
    if status == -1 && errno == EINTR {
        return []
    }
    #endif
    throw SocketError.makeFailed("poll.getNotifications poll")
}

Whether WSAEINTR should also be swallowed on Windows is left as a separate decision. Happy to send a PR for the POSIX-only version if this direction looks right.

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions