select: use poll() if existing, avoid poll() with no sockets

poll() on macOS 10.12 was deemed broken in 2016 when we discovered that
it misbehaves when provided with no sockets to wait for. The
HAVE_POLL_FINE is used to mark a poll() implementation that behaves
correctly: it *should* still wait the timeout time.

curl has therefore opted to use select() on Apple operating systems ever
since. To avoid the risk that this or other breakage cause problems.

However, using select() internally is also bad because it suffers from
problems when using file descriptors beyond 1024.

This change makes poll() used if it is present, but if there is no
sockets to wait for it avoids using poll() and instead falls back to
select() - but without any sockets to wait for there is no 1024 problem.

This removes all previous special-handling involving HAVE_POLL_FINE.

ref: https://daniel.haxx.se/blog/2016/10/11/poll-on-mac-10-12-is-broken/

Closes #15096
This commit is contained in:
Daniel Stenberg 2024-09-30 23:43:58 +02:00
parent 72d2090fc2
commit c72cefea0f
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
12 changed files with 42 additions and 165 deletions

View File

@ -76,35 +76,6 @@ check_c_source_compiles("${_source_epilogue}
unset(CMAKE_TRY_COMPILE_TARGET_TYPE) unset(CMAKE_TRY_COMPILE_TARGET_TYPE)
if(NOT APPLE)
set(_source_epilogue "#undef inline")
add_header_include(HAVE_SYS_POLL_H "sys/poll.h")
add_header_include(HAVE_POLL_H "poll.h")
if(NOT CMAKE_CROSSCOMPILING)
check_c_source_runs("${_source_epilogue}
#include <stdlib.h>
int main(void)
{
if(0 != poll(0, 0, 10)) {
return 1; /* fail */
}
return 0;
}" HAVE_POLL_FINE)
elseif(UNIX)
check_c_source_compiles("${_source_epilogue}
#include <stdlib.h>
int main(void)
{
#if defined(__BIONIC__) || \
defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L
(void)poll(0, 0, 0);
#else
#error force compilation error
#endif
}" HAVE_POLL_FINE)
endif()
endif()
# Detect HAVE_GETADDRINFO_THREADSAFE # Detect HAVE_GETADDRINFO_THREADSAFE
if(WIN32) if(WIN32)

View File

@ -128,7 +128,7 @@ set(HAVE_NETINET_UDP_H 0)
set(HAVE_NET_IF_H 0) set(HAVE_NET_IF_H 0)
set(HAVE_IOCTL_SIOCGIFADDR 0) set(HAVE_IOCTL_SIOCGIFADDR 0)
set(HAVE_POLL_H 0) set(HAVE_POLL_H 0)
set(HAVE_POLL_FINE 0) set(HAVE_POLL 0)
set(HAVE_PWD_H 0) set(HAVE_PWD_H 0)
set(HAVE_SYS_EVENTFD_H 0) set(HAVE_SYS_EVENTFD_H 0)
set(HAVE_SYS_FILIO_H 0) set(HAVE_SYS_FILIO_H 0)

View File

@ -1466,6 +1466,7 @@ endif()
check_symbol_exists("fnmatch" "${CURL_INCLUDES};fnmatch.h" HAVE_FNMATCH) check_symbol_exists("fnmatch" "${CURL_INCLUDES};fnmatch.h" HAVE_FNMATCH)
check_symbol_exists("basename" "${CURL_INCLUDES};string.h" HAVE_BASENAME) check_symbol_exists("basename" "${CURL_INCLUDES};string.h" HAVE_BASENAME)
check_symbol_exists("opendir" "${CURL_INCLUDES};dirent.h" HAVE_OPENDIR) check_symbol_exists("opendir" "${CURL_INCLUDES};dirent.h" HAVE_OPENDIR)
check_symbol_exists("poll" "${CURL_INCLUDES}" HAVE_POLL)
check_symbol_exists("socket" "${CURL_INCLUDES}" HAVE_SOCKET) check_symbol_exists("socket" "${CURL_INCLUDES}" HAVE_SOCKET)
check_symbol_exists("sched_yield" "${CURL_INCLUDES};sched.h" HAVE_SCHED_YIELD) check_symbol_exists("sched_yield" "${CURL_INCLUDES};sched.h" HAVE_SCHED_YIELD)
check_symbol_exists("socketpair" "${CURL_INCLUDES}" HAVE_SOCKETPAIR) check_symbol_exists("socketpair" "${CURL_INCLUDES}" HAVE_SOCKETPAIR)

View File

@ -4108,6 +4108,7 @@ AC_CHECK_FUNCS([\
if_nametoindex \ if_nametoindex \
mach_absolute_time \ mach_absolute_time \
pipe \ pipe \
poll \
sched_yield \ sched_yield \
sendmsg \ sendmsg \
sendmmsg \ sendmmsg \

View File

@ -103,7 +103,7 @@
#define USE_OPENSSL 1 #define USE_OPENSSL 1
#define HAVE_PIPE 1 #define HAVE_PIPE 1
#define HAVE_POLL_FINE 1 #define HAVE_POLL 1
#define HAVE_POLL_H 1 #define HAVE_POLL_H 1
#define HAVE_PTHREAD_H 1 #define HAVE_PTHREAD_H 1
#define HAVE_SETLOCALE 1 #define HAVE_SETLOCALE 1

View File

@ -430,8 +430,8 @@
/* Define to 1 if you have the `eventfd' function. */ /* Define to 1 if you have the `eventfd' function. */
#cmakedefine HAVE_EVENTFD 1 #cmakedefine HAVE_EVENTFD 1
/* If you have a fine poll */ /* If you have poll */
#cmakedefine HAVE_POLL_FINE 1 #cmakedefine HAVE_POLL 1
/* Define to 1 if you have the <poll.h> header file. */ /* Define to 1 if you have the <poll.h> header file. */
#cmakedefine HAVE_POLL_H 1 #cmakedefine HAVE_POLL_H 1

View File

@ -24,6 +24,10 @@
#include "curl_setup.h" #include "curl_setup.h"
#if !defined(HAVE_SELECT) && !defined(HAVE_POLL)
#error "We cannot compile without select() or poll() support."
#endif
#include <limits.h> #include <limits.h>
#ifdef HAVE_SYS_SELECT_H #ifdef HAVE_SYS_SELECT_H
@ -32,10 +36,6 @@
#include <unistd.h> #include <unistd.h>
#endif #endif
#if !defined(HAVE_SELECT) && !defined(HAVE_POLL_FINE)
#error "We cannot compile without select() or poll() support."
#endif
#ifdef MSDOS #ifdef MSDOS
#include <dos.h> /* delay() */ #include <dos.h> /* delay() */
#endif #endif
@ -53,16 +53,15 @@
#include "memdebug.h" #include "memdebug.h"
/* /*
* Internal function used for waiting a specific amount of ms * Internal function used for waiting a specific amount of ms in
* in Curl_socket_check() and Curl_poll() when no file descriptor * Curl_socket_check() and Curl_poll() when no file descriptor is provided to
* is provided to wait on, just being used to delay execution. * wait on, just being used to delay execution. Winsock select() and poll()
* Winsock select() and poll() timeout mechanisms need a valid * timeout mechanisms need a valid socket descriptor in a not null file
* socket descriptor in a not null file descriptor set to work. * descriptor set to work. Waiting indefinitely with this function is not
* Waiting indefinitely with this function is not allowed, a * allowed, a zero or negative timeout value will return immediately. Timeout
* zero or negative timeout value will return immediately. * resolution, accuracy, as well as maximum supported value is system
* Timeout resolution, accuracy, as well as maximum supported * dependent, neither factor is a critical issue for the intended use of this
* value is system dependent, neither factor is a critical issue * function in the library.
* for the intended use of this function in the library.
* *
* Return values: * Return values:
* -1 = system call error, or invalid timeout value * -1 = system call error, or invalid timeout value
@ -89,20 +88,13 @@ int Curl_wait_ms(timediff_t timeout_ms)
#endif #endif
Sleep((ULONG)timeout_ms); Sleep((ULONG)timeout_ms);
#else #else
#if defined(HAVE_POLL_FINE) /* avoid using poll() for this since it behaves incorrectly with no sockets
/* prevent overflow, timeout_ms is typecast to int. */ on Apple operating systems */
#if TIMEDIFF_T_MAX > INT_MAX
if(timeout_ms > INT_MAX)
timeout_ms = INT_MAX;
#endif
r = poll(NULL, 0, (int)timeout_ms);
#else
{ {
struct timeval pending_tv; struct timeval pending_tv;
r = select(0, NULL, NULL, NULL, curlx_mstotv(&pending_tv, timeout_ms)); r = select(0, NULL, NULL, NULL, curlx_mstotv(&pending_tv, timeout_ms));
} }
#endif /* HAVE_POLL_FINE */ #endif /* _WIN32 */
#endif /* USE_WINSOCK */
if(r) { if(r) {
if((r == -1) && (SOCKERRNO == EINTR)) if((r == -1) && (SOCKERRNO == EINTR))
/* make EINTR from select or poll not a "lethal" error */ /* make EINTR from select or poll not a "lethal" error */
@ -113,12 +105,12 @@ int Curl_wait_ms(timediff_t timeout_ms)
return r; return r;
} }
#ifndef HAVE_POLL_FINE #ifndef HAVE_POLL
/* /*
* This is a wrapper around select() to aid in Windows compatibility. * This is a wrapper around select() to aid in Windows compatibility. A
* A negative timeout value makes this function wait indefinitely, * negative timeout value makes this function wait indefinitely, unless no
* unless no valid file descriptor is given, when this happens the * valid file descriptor is given, when this happens the negative timeout is
* negative timeout is ignored and the function times out immediately. * ignored and the function times out immediately.
* *
* Return values: * Return values:
* -1 = system call error or fd >= FD_SETSIZE * -1 = system call error or fd >= FD_SETSIZE
@ -172,13 +164,13 @@ static int our_select(curl_socket_t maxfd, /* highest socket number */
/* /*
* Wait for read or write events on a set of file descriptors. It uses poll() * Wait for read or write events on a set of file descriptors. It uses poll()
* when a fine poll() is available, in order to avoid limits with FD_SETSIZE, * when poll() is available, in order to avoid limits with FD_SETSIZE,
* otherwise select() is used. An error is returned if select() is being used * otherwise select() is used. An error is returned if select() is being used
* and a file descriptor is too large for FD_SETSIZE. * and a file descriptor is too large for FD_SETSIZE.
* *
* A negative timeout value makes this function wait indefinitely, * A negative timeout value makes this function wait indefinitely, unless no
* unless no valid file descriptor is given, when this happens the * valid file descriptor is given, when this happens the negative timeout is
* negative timeout is ignored and the function times out immediately. * ignored and the function times out immediately.
* *
* Return values: * Return values:
* -1 = system call error or fd >= FD_SETSIZE * -1 = system call error or fd >= FD_SETSIZE
@ -275,7 +267,7 @@ int Curl_socket_check(curl_socket_t readfd0, /* two sockets to read from */
*/ */
int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms) int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms)
{ {
#ifdef HAVE_POLL_FINE #ifdef HAVE_POLL
int pending_ms; int pending_ms;
#else #else
fd_set fds_read; fd_set fds_read;
@ -305,7 +297,7 @@ int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms)
when function is called with a zero timeout or a negative timeout when function is called with a zero timeout or a negative timeout
value indicating a blocking call should be performed. */ value indicating a blocking call should be performed. */
#ifdef HAVE_POLL_FINE #ifdef HAVE_POLL
/* prevent overflow, timeout_ms is typecast to int. */ /* prevent overflow, timeout_ms is typecast to int. */
#if TIMEDIFF_T_MAX > INT_MAX #if TIMEDIFF_T_MAX > INT_MAX
@ -335,7 +327,7 @@ int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms)
ufds[i].revents |= POLLIN|POLLOUT; ufds[i].revents |= POLLIN|POLLOUT;
} }
#else /* HAVE_POLL_FINE */ #else /* HAVE_POLL */
FD_ZERO(&fds_read); FD_ZERO(&fds_read);
FD_ZERO(&fds_write); FD_ZERO(&fds_write);
@ -401,7 +393,7 @@ int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms)
r++; r++;
} }
#endif /* HAVE_POLL_FINE */ #endif /* HAVE_POLL */
return r; return r;
} }

View File

@ -3407,21 +3407,6 @@ AC_DEFUN([CURL_CHECK_FUNC_POLL], [
tst_links_poll="unknown" tst_links_poll="unknown"
tst_proto_poll="unknown" tst_proto_poll="unknown"
tst_compi_poll="unknown" tst_compi_poll="unknown"
tst_works_poll="unknown"
tst_allow_poll="unknown"
#
case $host in
*-apple-*|*-*-interix*)
dnl poll() does not work on these platforms
dnl Interix: "does provide poll(), but the implementing developer must
dnl have been in a bad mood, because poll() only works on the /proc
dnl filesystem here"
dnl macOS: poll() first didn't exist, then was broken until fixed in 10.9
dnl only to break again in 10.12.
curl_disallow_poll="yes"
tst_compi_poll="no"
;;
esac
# #
AC_MSG_CHECKING([if poll can be linked]) AC_MSG_CHECKING([if poll can be linked])
AC_LINK_IFELSE([ AC_LINK_IFELSE([
@ -3464,82 +3449,13 @@ AC_DEFUN([CURL_CHECK_FUNC_POLL], [
],[ ],[
AC_MSG_RESULT([yes]) AC_MSG_RESULT([yes])
tst_compi_poll="yes" tst_compi_poll="yes"
AC_DEFINE_UNQUOTED(HAVE_POLL, 1, [If you have poll])
],[ ],[
AC_MSG_RESULT([no]) AC_MSG_RESULT([no])
tst_compi_poll="no" tst_compi_poll="no"
]) ])
fi fi
# #
dnl only do runtime verification when not cross-compiling
if test "$tst_compi_poll" = "yes"; then
if test "x$cross_compiling" != "xyes"; then
AC_MSG_CHECKING([if poll seems to work])
CURL_RUN_IFELSE([
AC_LANG_PROGRAM([[
$curl_includes_stdlib
$curl_includes_poll
]],[[
/* detect the original poll() breakage */
if(0 != poll(0, 0, 10)) {
return 1; /* fail */
}
]])
],[
AC_MSG_RESULT([yes])
tst_works_poll="yes"
],[
AC_MSG_RESULT([no])
tst_works_poll="no"
])
else
AC_MSG_CHECKING([if native poll seems to be supported])
AC_COMPILE_IFELSE([
AC_LANG_PROGRAM([[
$curl_includes_stdlib
]],[[
#if defined(__BIONIC__) || \
(defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L)
return 0;
#else
#error force compilation error
#endif
]])
],[
AC_MSG_RESULT([yes])
tst_works_poll="yes"
],[
AC_MSG_RESULT([no])
tst_works_poll="no"
])
fi
fi
#
if test "$tst_compi_poll" = "yes" &&
test "$tst_works_poll" != "no"; then
AC_MSG_CHECKING([if poll usage allowed])
if test "x$curl_disallow_poll" != "xyes"; then
AC_MSG_RESULT([yes])
tst_allow_poll="yes"
else
AC_MSG_RESULT([no])
tst_allow_poll="no"
fi
fi
#
AC_MSG_CHECKING([if poll might be used])
if test "$tst_links_poll" = "yes" &&
test "$tst_proto_poll" = "yes" &&
test "$tst_compi_poll" = "yes" &&
test "$tst_allow_poll" = "yes" &&
test "$tst_works_poll" != "no"; then
AC_MSG_RESULT([yes])
AC_DEFINE_UNQUOTED(HAVE_POLL_FINE, 1,
[If you have a fine poll])
curl_cv_func_poll="yes"
else
AC_MSG_RESULT([no])
curl_cv_func_poll="no"
fi
]) ])

View File

@ -49,8 +49,6 @@ void tool_go_sleep(long ms)
delay(ms); delay(ms);
#elif defined(_WIN32) #elif defined(_WIN32)
Sleep((DWORD)ms); Sleep((DWORD)ms);
#elif defined(HAVE_POLL_FINE)
(void)poll((void *)0, 0, (int)ms);
#else #else
struct timeval timeout; struct timeval timeout;
timeout.tv_sec = ms / 1000L; timeout.tv_sec = ms / 1000L;

View File

@ -368,7 +368,7 @@ static int test_rlimit(int keep_open)
rlim2str(strbuff, sizeof(strbuff), num_open.rlim_max); rlim2str(strbuff, sizeof(strbuff), num_open.rlim_max);
fprintf(stderr, "%s file descriptors open\n", strbuff); fprintf(stderr, "%s file descriptors open\n", strbuff);
#if !defined(HAVE_POLL_FINE) && !defined(USE_WINSOCK) #if !defined(HAVE_POLL) && !defined(USE_WINSOCK)
/* /*
* when using select() instead of poll() we cannot test * when using select() instead of poll() we cannot test

View File

@ -34,9 +34,7 @@
#include "warnless.h" #include "warnless.h"
#include "memdebug.h" #include "memdebug.h"
#if !defined(HAVE_POLL_FINE) && \ #if !defined(HAVE_POLL) && !defined(USE_WINSOCK) && !defined(FD_SETSIZE)
!defined(USE_WINSOCK) && \
!defined(FD_SETSIZE)
#error "this test requires FD_SETSIZE" #error "this test requires FD_SETSIZE"
#endif #endif
@ -381,7 +379,7 @@ static int test_rlimit(int keep_open)
rlim2str(strbuff, sizeof(strbuff), num_open.rlim_max); rlim2str(strbuff, sizeof(strbuff), num_open.rlim_max);
fprintf(stderr, "%s file descriptors open\n", strbuff); fprintf(stderr, "%s file descriptors open\n", strbuff);
#if !defined(HAVE_POLL_FINE) && !defined(USE_WINSOCK) #if !defined(HAVE_POLL) && !defined(USE_WINSOCK)
/* /*
* when using select() instead of poll() we cannot test * when using select() instead of poll() we cannot test

View File

@ -229,7 +229,7 @@ FILE *test2fopen(long testno, const char *logdir)
int wait_ms(int timeout_ms) int wait_ms(int timeout_ms)
{ {
#if !defined(MSDOS) && !defined(USE_WINSOCK) #if !defined(MSDOS) && !defined(USE_WINSOCK)
#ifndef HAVE_POLL_FINE #ifndef HAVE_POLL
struct timeval pending_tv; struct timeval pending_tv;
#endif #endif
struct timeval initial_tv; struct timeval initial_tv;
@ -252,13 +252,13 @@ int wait_ms(int timeout_ms)
initial_tv = tvnow(); initial_tv = tvnow();
do { do {
int error; int error;
#if defined(HAVE_POLL_FINE) #ifdef HAVE_POLL
r = poll(NULL, 0, pending_ms); r = poll(NULL, 0, pending_ms);
#else #else
pending_tv.tv_sec = pending_ms / 1000; pending_tv.tv_sec = pending_ms / 1000;
pending_tv.tv_usec = (pending_ms % 1000) * 1000; pending_tv.tv_usec = (pending_ms % 1000) * 1000;
r = select(0, NULL, NULL, NULL, &pending_tv); r = select(0, NULL, NULL, NULL, &pending_tv);
#endif /* HAVE_POLL_FINE */ #endif /* HAVE_POLL */
if(r != -1) if(r != -1)
break; break;
error = errno; error = errno;