Skip to content

curl-sys: define HAVE_POLL so vendored curl >= 8.11 uses poll() instead of select() - #685

Open
rlgrpe wants to merge 1 commit into
sagebind:mainfrom
rlgrpe:fix/have-poll-0.4.83
Open

rlgrpe wants to merge 1 commit into
sagebind:mainfrom
rlgrpe:fix/have-poll-0.4.83

Conversation

@rlgrpe

@rlgrpe rlgrpe commented Oct 9, 2026

Copy link
Copy Markdown

Fixes #684

What

Defines HAVE_POLL in curl-sys/build.rs for non-Apple Unix targets, next to the existing HAVE_POLL_FINE.

Why

curl commit c72cefea0 (first released in curl 8.11.0) changed lib/select.c to gate the poll() implementation on HAVE_POLL; HAVE_POLL_FINE is the pre-8.11 name and is no longer referenced anywhere under curl/lib/. Since build.rs only defines the old name, every vendored (static-curl) build on Linux and other non-Apple Unix targets has been compiling libcurl with the select() fallback.

In that fallback Curl_poll() runs VERIFY_SOCK(), which rejects any descriptor >= FD_SETSIZE (1024). The effect is that once a process has more than ~1024 open file descriptors, new connections landing on a high fd number fail right after a successful TCP handshake with CURLE_COULDNT_CONNECT ("Could not connect to server"), with no further detail because verifyconnect() overwrites the errno with the (zero) SO_ERROR. Details, strace output and the fd-number split are in #684.

HAVE_POLL_FINE is kept so that pinning an older curl checkout keeps working. Apple targets are intentionally unchanged.

Verification

Built a binary against the patched crate (curl 8.15.0 vendored) and traced it: poll() now appears before getsockopt(SOL_SOCKET, SO_ERROR) for every new socket, and sockets with fd >= 1024 connect and send normally. Without the patch the same binary shows getsockopt immediately followed by close() and the transfer fails with error 7.

The change is identical on main (curl 8.22); the branch is based on the 0.4.83+curl-8.15.0 release commit (8b34786) so it can also be consumed as a [patch.crates-io] override for that pinned release.

curl >= 8.x gates lib/select.c on HAVE_POLL; HAVE_POLL_FINE is the pre-8.x
name. Without HAVE_POLL libcurl falls back to select(), and VERIFY_SOCK
rejects any fd >= FD_SETSIZE (1024): the connect succeeds, SO_ERROR is 0,
and cf_socket_connect still returns CURLE_COULDNT_CONNECT and closes the
socket. Observed in production with ~130+ concurrent proxied clients.
@rlgrpe

rlgrpe commented Oct 9, 2026

Copy link
Copy Markdown
Author

Heads-up for reviewers and for anyone consuming this through isahc: the HAVE_POLL define is correct and necessary, but on its own it unmasks a second bug in isahc's socket selector that crashes the process. Details and the exact one-line change isahc needs are in #684 (comment).

Short version: with select() any connection on fd >= 1024 died at connect and never reached isahc's epoll registration. With poll() those sockets go through the full register/deregister cycle, so the pre-existing race "curl reports a socket it already closed, the fd number was meanwhile reused by a regular file" now fires. epoll_ctl answers that with EPERM, which isahc/src/agent/selector.rs::is_bad_socket_error does not recognise (it only defers EBADF/ENOENT), the agent thread hits unwrap() at agent/mod.rs:548, and the process goes down with SIGSEGV while the multi handle unwinds. Observed ~6 h after deploying this patch in the process from #684.

The isahc fix is to treat EPERM like EBADF in is_bad_socket_error so the descriptor is parked in bad_sockets and retried; nothing in this PR needs to change.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

1 participant