https://github.com/python/cpython/commit/3514ba2175764e4d746e6862e2fcfc06b5fcd6c2
commit: 3514ba2175764e4d746e6862e2fcfc06b5fcd6c2
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-01-15T13:49:46+01:00
summary:
gh-142434: Use ppoll() if available in select.poll (#143529)
files:
A Misc/NEWS.d/next/Library/2026-01-07-19-01-59.gh-issue-142434.SHRS5p.rst
M Doc/library/select.rst
M Lib/test/test_poll.py
M Modules/_testcapimodule.c
M Modules/clinic/selectmodule.c.h
M Modules/selectmodule.c
M configure
M configure.ac
M pyconfig.h.in
diff --git a/Doc/library/select.rst b/Doc/library/select.rst
index 62b5161fb80634..ce4e92654d5932 100644
--- a/Doc/library/select.rst
+++ b/Doc/library/select.rst
@@ -478,6 +478,8 @@ linearly scanned again. :c:func:`!select` is *O*\ (*highest
file descriptor*), w
.. versionchanged:: 3.15
Accepts any real number as *timeout*, not only integer or float.
+ If ``ppoll()`` function is available, *timeout* has a resolution
+ of ``1`` ns (``1e-6`` ms) instead of ``1`` ms.
.. _kqueue-objects:
diff --git a/Lib/test/test_poll.py b/Lib/test/test_poll.py
index 5675db8d1cab6e..a15612b91458fa 100644
--- a/Lib/test/test_poll.py
+++ b/Lib/test/test_poll.py
@@ -173,17 +173,25 @@ def test_poll3(self):
@cpython_only
def test_poll_c_limits(self):
try:
+ import _testcapi
from _testcapi import USHRT_MAX, INT_MAX, UINT_MAX
+ HAVE_PPOLL = getattr(_testcapi, 'HAVE_PPOLL', False)
except ImportError:
raise unittest.SkipTest("requires _testcapi")
+
pollster = select.poll()
pollster.register(1)
# Issues #15989, #17919
self.assertRaises(OverflowError, pollster.register, 0, USHRT_MAX + 1)
self.assertRaises(OverflowError, pollster.modify, 1, USHRT_MAX + 1)
- self.assertRaises(OverflowError, pollster.poll, INT_MAX + 1)
- self.assertRaises(OverflowError, pollster.poll, UINT_MAX + 1)
+ if HAVE_PPOLL:
+ MS_TO_NS = 1_000_000
+ tmax = _testcapi.INT64_MAX // MS_TO_NS
+ self.assertRaises(OverflowError, pollster.poll, tmax + 1)
+ else:
+ self.assertRaises(OverflowError, pollster.poll, INT_MAX + 1)
+ self.assertRaises(OverflowError, pollster.poll, UINT_MAX + 1)
@threading_helper.reap_threads
def test_threaded_poll(self):
diff --git
a/Misc/NEWS.d/next/Library/2026-01-07-19-01-59.gh-issue-142434.SHRS5p.rst
b/Misc/NEWS.d/next/Library/2026-01-07-19-01-59.gh-issue-142434.SHRS5p.rst
new file mode 100644
index 00000000000000..cb6990a463bdd9
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-01-07-19-01-59.gh-issue-142434.SHRS5p.rst
@@ -0,0 +1,3 @@
+Use ``ppoll()`` if available in :func:`select.poll` to have a timeout
+resolution of 1 nanosecond, instead of a resolution of 1 ms. Patch by Victor
+Stinner.
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index de6d3cbce54fbe..c0ab35cda191c8 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -3359,6 +3359,12 @@ _testcapi_exec(PyObject *m)
PyModule_AddObject(m, "INT64_MAX", PyLong_FromInt64(INT64_MAX));
PyModule_AddObject(m, "UINT64_MAX", PyLong_FromUInt64(UINT64_MAX));
+#ifdef HAVE_PPOLL
+ if (PyModule_AddObjectRef(m, "HAVE_PPOLL", Py_True) < 0) {
+ return -1;
+ }
+#endif
+
if (PyModule_AddIntMacro(m, _Py_STACK_GROWS_DOWN)) {
return -1;
}
diff --git a/Modules/clinic/selectmodule.c.h b/Modules/clinic/selectmodule.c.h
index 26ddc6ffba75bf..3952054e9e32bf 100644
--- a/Modules/clinic/selectmodule.c.h
+++ b/Modules/clinic/selectmodule.c.h
@@ -70,7 +70,7 @@ select_select(PyObject *module, PyObject *const *args,
Py_ssize_t nargs)
return return_value;
}
-#if (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL))
+#if ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) && !defined(HAVE_BROKEN_POLL))
PyDoc_STRVAR(select_poll_register__doc__,
"register($self, fd,\n"
@@ -119,9 +119,9 @@ select_poll_register(PyObject *self, PyObject *const *args,
Py_ssize_t nargs)
return return_value;
}
-#endif /* (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)) */
+#endif /* ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) &&
!defined(HAVE_BROKEN_POLL)) */
-#if (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL))
+#if ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) && !defined(HAVE_BROKEN_POLL))
PyDoc_STRVAR(select_poll_modify__doc__,
"modify($self, fd, eventmask, /)\n"
@@ -166,9 +166,9 @@ select_poll_modify(PyObject *self, PyObject *const *args,
Py_ssize_t nargs)
return return_value;
}
-#endif /* (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)) */
+#endif /* ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) &&
!defined(HAVE_BROKEN_POLL)) */
-#if (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL))
+#if ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) && !defined(HAVE_BROKEN_POLL))
PyDoc_STRVAR(select_poll_unregister__doc__,
"unregister($self, fd, /)\n"
@@ -200,9 +200,9 @@ select_poll_unregister(PyObject *self, PyObject *arg)
return return_value;
}
-#endif /* (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)) */
+#endif /* ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) &&
!defined(HAVE_BROKEN_POLL)) */
-#if (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL))
+#if ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) && !defined(HAVE_BROKEN_POLL))
PyDoc_STRVAR(select_poll_poll__doc__,
"poll($self, timeout=None, /)\n"
@@ -245,9 +245,9 @@ select_poll_poll(PyObject *self, PyObject *const *args,
Py_ssize_t nargs)
return return_value;
}
-#endif /* (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)) */
+#endif /* ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) &&
!defined(HAVE_BROKEN_POLL)) */
-#if (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)) &&
defined(HAVE_SYS_DEVPOLL_H)
+#if ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) &&
!defined(HAVE_BROKEN_POLL)) && defined(HAVE_SYS_DEVPOLL_H)
PyDoc_STRVAR(select_devpoll_register__doc__,
"register($self, fd,\n"
@@ -298,9 +298,9 @@ select_devpoll_register(PyObject *self, PyObject *const
*args, Py_ssize_t nargs)
return return_value;
}
-#endif /* (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)) &&
defined(HAVE_SYS_DEVPOLL_H) */
+#endif /* ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) &&
!defined(HAVE_BROKEN_POLL)) && defined(HAVE_SYS_DEVPOLL_H) */
-#if (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)) &&
defined(HAVE_SYS_DEVPOLL_H)
+#if ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) &&
!defined(HAVE_BROKEN_POLL)) && defined(HAVE_SYS_DEVPOLL_H)
PyDoc_STRVAR(select_devpoll_modify__doc__,
"modify($self, fd,\n"
@@ -351,9 +351,9 @@ select_devpoll_modify(PyObject *self, PyObject *const
*args, Py_ssize_t nargs)
return return_value;
}
-#endif /* (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)) &&
defined(HAVE_SYS_DEVPOLL_H) */
+#endif /* ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) &&
!defined(HAVE_BROKEN_POLL)) && defined(HAVE_SYS_DEVPOLL_H) */
-#if (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)) &&
defined(HAVE_SYS_DEVPOLL_H)
+#if ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) &&
!defined(HAVE_BROKEN_POLL)) && defined(HAVE_SYS_DEVPOLL_H)
PyDoc_STRVAR(select_devpoll_unregister__doc__,
"unregister($self, fd, /)\n"
@@ -385,9 +385,9 @@ select_devpoll_unregister(PyObject *self, PyObject *arg)
return return_value;
}
-#endif /* (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)) &&
defined(HAVE_SYS_DEVPOLL_H) */
+#endif /* ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) &&
!defined(HAVE_BROKEN_POLL)) && defined(HAVE_SYS_DEVPOLL_H) */
-#if (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)) &&
defined(HAVE_SYS_DEVPOLL_H)
+#if ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) &&
!defined(HAVE_BROKEN_POLL)) && defined(HAVE_SYS_DEVPOLL_H)
PyDoc_STRVAR(select_devpoll_poll__doc__,
"poll($self, timeout=None, /)\n"
@@ -430,9 +430,9 @@ select_devpoll_poll(PyObject *self, PyObject *const *args,
Py_ssize_t nargs)
return return_value;
}
-#endif /* (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)) &&
defined(HAVE_SYS_DEVPOLL_H) */
+#endif /* ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) &&
!defined(HAVE_BROKEN_POLL)) && defined(HAVE_SYS_DEVPOLL_H) */
-#if (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)) &&
defined(HAVE_SYS_DEVPOLL_H)
+#if ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) &&
!defined(HAVE_BROKEN_POLL)) && defined(HAVE_SYS_DEVPOLL_H)
PyDoc_STRVAR(select_devpoll_close__doc__,
"close($self, /)\n"
@@ -460,9 +460,9 @@ select_devpoll_close(PyObject *self, PyObject
*Py_UNUSED(ignored))
return return_value;
}
-#endif /* (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)) &&
defined(HAVE_SYS_DEVPOLL_H) */
+#endif /* ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) &&
!defined(HAVE_BROKEN_POLL)) && defined(HAVE_SYS_DEVPOLL_H) */
-#if (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)) &&
defined(HAVE_SYS_DEVPOLL_H)
+#if ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) &&
!defined(HAVE_BROKEN_POLL)) && defined(HAVE_SYS_DEVPOLL_H)
PyDoc_STRVAR(select_devpoll_fileno__doc__,
"fileno($self, /)\n"
@@ -488,9 +488,9 @@ select_devpoll_fileno(PyObject *self, PyObject
*Py_UNUSED(ignored))
return return_value;
}
-#endif /* (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)) &&
defined(HAVE_SYS_DEVPOLL_H) */
+#endif /* ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) &&
!defined(HAVE_BROKEN_POLL)) && defined(HAVE_SYS_DEVPOLL_H) */
-#if (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL))
+#if ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) && !defined(HAVE_BROKEN_POLL))
PyDoc_STRVAR(select_poll__doc__,
"poll($module, /)\n"
@@ -513,9 +513,9 @@ select_poll(PyObject *module, PyObject *Py_UNUSED(ignored))
return select_poll_impl(module);
}
-#endif /* (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)) */
+#endif /* ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) &&
!defined(HAVE_BROKEN_POLL)) */
-#if (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)) &&
defined(HAVE_SYS_DEVPOLL_H)
+#if ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) &&
!defined(HAVE_BROKEN_POLL)) && defined(HAVE_SYS_DEVPOLL_H)
PyDoc_STRVAR(select_devpoll__doc__,
"devpoll($module, /)\n"
@@ -538,7 +538,7 @@ select_devpoll(PyObject *module, PyObject
*Py_UNUSED(ignored))
return select_devpoll_impl(module);
}
-#endif /* (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)) &&
defined(HAVE_SYS_DEVPOLL_H) */
+#endif /* ((defined(HAVE_POLL) || defined(HAVE_PPOLL)) &&
!defined(HAVE_BROKEN_POLL)) && defined(HAVE_SYS_DEVPOLL_H) */
#if defined(HAVE_EPOLL)
@@ -1399,4 +1399,4 @@ select_kqueue_control(PyObject *self, PyObject *const
*args, Py_ssize_t nargs)
#ifndef SELECT_KQUEUE_CONTROL_METHODDEF
#define SELECT_KQUEUE_CONTROL_METHODDEF
#endif /* !defined(SELECT_KQUEUE_CONTROL_METHODDEF) */
-/*[clinic end generated code: output=ae54d65938513132 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=52e3be5cc66cf1b6 input=a9049054013a1b77]*/
diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c
index 19fe509ec5e32a..137bf2ca55bbf8 100644
--- a/Modules/selectmodule.c
+++ b/Modules/selectmodule.c
@@ -427,7 +427,7 @@ select_select_impl(PyObject *module, PyObject *rlist,
PyObject *wlist,
return ret;
}
-#if defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)
+#if (defined(HAVE_POLL) || defined(HAVE_PPOLL)) && !defined(HAVE_BROKEN_POLL)
/*
* poll() support
*/
@@ -626,7 +626,7 @@ select_poll_poll_impl(pollObject *self, PyObject
*timeout_obj)
PyObject *result_list = NULL;
int poll_result, i, j;
PyObject *value = NULL, *num = NULL;
- PyTime_t timeout = -1, ms = -1, deadline = 0;
+ PyTime_t timeout = -1, deadline = 0;
int async_err = 0;
if (timeout_obj != Py_None) {
@@ -639,15 +639,28 @@ select_poll_poll_impl(pollObject *self, PyObject
*timeout_obj)
}
return NULL;
}
+ }
- ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_TIMEOUT);
- if (ms < INT_MIN || ms > INT_MAX) {
- PyErr_SetString(PyExc_OverflowError, "timeout is too large");
+#ifdef HAVE_PPOLL
+ struct timespec ts, *ts_p = NULL;
+
+ if (timeout_obj != Py_None) {
+ if (_PyTime_AsTimespec(timeout, &ts) < 0) {
return NULL;
}
if (timeout >= 0) {
- deadline = _PyDeadline_Init(timeout);
+ ts_p = &ts;
+ }
+ }
+#else
+ PyTime_t ms = -1;
+
+ if (timeout_obj != Py_None) {
+ ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_TIMEOUT);
+ if (ms < INT_MIN || ms > INT_MAX) {
+ PyErr_SetString(PyExc_OverflowError, "timeout is too large");
+ return NULL;
}
}
@@ -661,6 +674,11 @@ select_poll_poll_impl(pollObject *self, PyObject
*timeout_obj)
ms = -1;
#endif
}
+#endif
+
+ if (timeout >= 0) {
+ deadline = _PyDeadline_Init(timeout);
+ }
/* Avoid concurrent poll() invocation, issue 8865 */
if (self->poll_running) {
@@ -681,7 +699,11 @@ select_poll_poll_impl(pollObject *self, PyObject
*timeout_obj)
do {
Py_BEGIN_ALLOW_THREADS
errno = 0;
+#ifdef HAVE_PPOLL
+ poll_result = ppoll(self->ufds, self->ufd_len, ts_p, NULL);
+#else
poll_result = poll(self->ufds, self->ufd_len, (int)ms);
+#endif
Py_END_ALLOW_THREADS
if (errno != EINTR)
@@ -699,8 +721,16 @@ select_poll_poll_impl(pollObject *self, PyObject
*timeout_obj)
poll_result = 0;
break;
}
+#ifdef HAVE_PPOLL
+ if (_PyTime_AsTimespec(timeout, &ts) < 0) {
+ poll_result = -1;
+ break;
+ }
+ assert(ts_p == &ts);
+#else
ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_CEILING);
- /* retry poll() with the recomputed timeout */
+#endif
+ /* retry poll()/ppoll() with the recomputed timeout */
}
} while (1);
@@ -2466,7 +2496,7 @@ static PyGetSetDef kqueue_queue_getsetlist[] = {
#include "clinic/selectmodule.c.h"
-#if defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)
+#if (defined(HAVE_POLL) || defined(HAVE_PPOLL)) && !defined(HAVE_BROKEN_POLL)
static PyMethodDef poll_methods[] = {
SELECT_POLL_REGISTER_METHODDEF
@@ -2661,7 +2691,7 @@ _select_exec(PyObject *m)
ADD_INT(PIPE_BUF);
#endif
-#if defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)
+#if (defined(HAVE_POLL) || defined(HAVE_PPOLL)) && !defined(HAVE_BROKEN_POLL)
#ifdef __APPLE__
if (select_have_broken_poll()) {
if (PyObject_DelAttrString(m, "poll") == -1) {
diff --git a/configure b/configure
index 9d7fe6c52a418a..c826a1bb85667b 100755
--- a/configure
+++ b/configure
@@ -19811,6 +19811,12 @@ if test "x$ac_cv_func_poll" = xyes
then :
printf "%s\n" "#define HAVE_POLL 1" >>confdefs.h
+fi
+ac_fn_c_check_func "$LINENO" "ppoll" "ac_cv_func_ppoll"
+if test "x$ac_cv_func_ppoll" = xyes
+then :
+ printf "%s\n" "#define HAVE_PPOLL 1" >>confdefs.h
+
fi
ac_fn_c_check_func "$LINENO" "posix_fadvise" "ac_cv_func_posix_fadvise"
if test "x$ac_cv_func_posix_fadvise" = xyes
diff --git a/configure.ac b/configure.ac
index 8c421c2a2baaf6..322d33dd0e3c99 100644
--- a/configure.ac
+++ b/configure.ac
@@ -5253,7 +5253,7 @@ AC_CHECK_FUNCS([ \
getspnam getuid getwd grantpt if_nameindex initgroups kill killpg lchown
linkat \
lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \
mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \
- pipe2 plock poll posix_fadvise posix_fallocate posix_openpt posix_spawn
posix_spawnp \
+ pipe2 plock poll ppoll posix_fadvise posix_fallocate posix_openpt
posix_spawn posix_spawnp \
posix_spawn_file_actions_addclosefrom_np \
pread preadv preadv2 process_vm_readv \
pthread_cond_timedwait_relative_np pthread_condattr_setclock pthread_init \
diff --git a/pyconfig.h.in b/pyconfig.h.in
index aabf9f0be8da55..4ae2abeabf1d41 100644
--- a/pyconfig.h.in
+++ b/pyconfig.h.in
@@ -979,6 +979,9 @@
function. */
#undef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP
+/* Define to 1 if you have the 'ppoll' function. */
+#undef HAVE_PPOLL
+
/* Define to 1 if you have the 'pread' function. */
#undef HAVE_PREAD
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]