Skip to content

Commit 3514ba2

Browse files
authored
gh-142434: Use ppoll() if available in select.poll (#143529)
1 parent a009e78 commit 3514ba2

File tree

9 files changed

+95
-37
lines changed

9 files changed

+95
-37
lines changed

Doc/library/select.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,8 @@ linearly scanned again. :c:func:`!select` is *O*\ (*highest file descriptor*), w
478478

479479
.. versionchanged:: 3.15
480480
Accepts any real number as *timeout*, not only integer or float.
481+
If ``ppoll()`` function is available, *timeout* has a resolution
482+
of ``1`` ns (``1e-6`` ms) instead of ``1`` ms.
481483

482484

483485
.. _kqueue-objects:

Lib/test/test_poll.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,17 +173,25 @@ def test_poll3(self):
173173
@cpython_only
174174
def test_poll_c_limits(self):
175175
try:
176+
import _testcapi
176177
from _testcapi import USHRT_MAX, INT_MAX, UINT_MAX
178+
HAVE_PPOLL = getattr(_testcapi, 'HAVE_PPOLL', False)
177179
except ImportError:
178180
raise unittest.SkipTest("requires _testcapi")
181+
179182
pollster = select.poll()
180183
pollster.register(1)
181184

182185
# Issues #15989, #17919
183186
self.assertRaises(OverflowError, pollster.register, 0, USHRT_MAX + 1)
184187
self.assertRaises(OverflowError, pollster.modify, 1, USHRT_MAX + 1)
185-
self.assertRaises(OverflowError, pollster.poll, INT_MAX + 1)
186-
self.assertRaises(OverflowError, pollster.poll, UINT_MAX + 1)
188+
if HAVE_PPOLL:
189+
MS_TO_NS = 1_000_000
190+
tmax = _testcapi.INT64_MAX // MS_TO_NS
191+
self.assertRaises(OverflowError, pollster.poll, tmax + 1)
192+
else:
193+
self.assertRaises(OverflowError, pollster.poll, INT_MAX + 1)
194+
self.assertRaises(OverflowError, pollster.poll, UINT_MAX + 1)
187195

188196
@threading_helper.reap_threads
189197
def test_threaded_poll(self):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Use ``ppoll()`` if available in :func:`select.poll` to have a timeout
2+
resolution of 1 nanosecond, instead of a resolution of 1 ms. Patch by Victor
3+
Stinner.

Modules/_testcapimodule.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3359,6 +3359,12 @@ _testcapi_exec(PyObject *m)
33593359
PyModule_AddObject(m, "INT64_MAX", PyLong_FromInt64(INT64_MAX));
33603360
PyModule_AddObject(m, "UINT64_MAX", PyLong_FromUInt64(UINT64_MAX));
33613361

3362+
#ifdef HAVE_PPOLL
3363+
if (PyModule_AddObjectRef(m, "HAVE_PPOLL", Py_True) < 0) {
3364+
return -1;
3365+
}
3366+
#endif
3367+
33623368
if (PyModule_AddIntMacro(m, _Py_STACK_GROWS_DOWN)) {
33633369
return -1;
33643370
}

Modules/clinic/selectmodule.c.h

Lines changed: 25 additions & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/selectmodule.c

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ select_select_impl(PyObject *module, PyObject *rlist, PyObject *wlist,
427427
return ret;
428428
}
429429

430-
#if defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)
430+
#if (defined(HAVE_POLL) || defined(HAVE_PPOLL)) && !defined(HAVE_BROKEN_POLL)
431431
/*
432432
* poll() support
433433
*/
@@ -626,7 +626,7 @@ select_poll_poll_impl(pollObject *self, PyObject *timeout_obj)
626626
PyObject *result_list = NULL;
627627
int poll_result, i, j;
628628
PyObject *value = NULL, *num = NULL;
629-
PyTime_t timeout = -1, ms = -1, deadline = 0;
629+
PyTime_t timeout = -1, deadline = 0;
630630
int async_err = 0;
631631

632632
if (timeout_obj != Py_None) {
@@ -639,15 +639,28 @@ select_poll_poll_impl(pollObject *self, PyObject *timeout_obj)
639639
}
640640
return NULL;
641641
}
642+
}
642643

643-
ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_TIMEOUT);
644-
if (ms < INT_MIN || ms > INT_MAX) {
645-
PyErr_SetString(PyExc_OverflowError, "timeout is too large");
644+
#ifdef HAVE_PPOLL
645+
struct timespec ts, *ts_p = NULL;
646+
647+
if (timeout_obj != Py_None) {
648+
if (_PyTime_AsTimespec(timeout, &ts) < 0) {
646649
return NULL;
647650
}
648651

649652
if (timeout >= 0) {
650-
deadline = _PyDeadline_Init(timeout);
653+
ts_p = &ts;
654+
}
655+
}
656+
#else
657+
PyTime_t ms = -1;
658+
659+
if (timeout_obj != Py_None) {
660+
ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_TIMEOUT);
661+
if (ms < INT_MIN || ms > INT_MAX) {
662+
PyErr_SetString(PyExc_OverflowError, "timeout is too large");
663+
return NULL;
651664
}
652665
}
653666

@@ -661,6 +674,11 @@ select_poll_poll_impl(pollObject *self, PyObject *timeout_obj)
661674
ms = -1;
662675
#endif
663676
}
677+
#endif
678+
679+
if (timeout >= 0) {
680+
deadline = _PyDeadline_Init(timeout);
681+
}
664682

665683
/* Avoid concurrent poll() invocation, issue 8865 */
666684
if (self->poll_running) {
@@ -681,7 +699,11 @@ select_poll_poll_impl(pollObject *self, PyObject *timeout_obj)
681699
do {
682700
Py_BEGIN_ALLOW_THREADS
683701
errno = 0;
702+
#ifdef HAVE_PPOLL
703+
poll_result = ppoll(self->ufds, self->ufd_len, ts_p, NULL);
704+
#else
684705
poll_result = poll(self->ufds, self->ufd_len, (int)ms);
706+
#endif
685707
Py_END_ALLOW_THREADS
686708

687709
if (errno != EINTR)
@@ -699,8 +721,16 @@ select_poll_poll_impl(pollObject *self, PyObject *timeout_obj)
699721
poll_result = 0;
700722
break;
701723
}
724+
#ifdef HAVE_PPOLL
725+
if (_PyTime_AsTimespec(timeout, &ts) < 0) {
726+
poll_result = -1;
727+
break;
728+
}
729+
assert(ts_p == &ts);
730+
#else
702731
ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_CEILING);
703-
/* retry poll() with the recomputed timeout */
732+
#endif
733+
/* retry poll()/ppoll() with the recomputed timeout */
704734
}
705735
} while (1);
706736

@@ -2466,7 +2496,7 @@ static PyGetSetDef kqueue_queue_getsetlist[] = {
24662496

24672497
#include "clinic/selectmodule.c.h"
24682498

2469-
#if defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)
2499+
#if (defined(HAVE_POLL) || defined(HAVE_PPOLL)) && !defined(HAVE_BROKEN_POLL)
24702500

24712501
static PyMethodDef poll_methods[] = {
24722502
SELECT_POLL_REGISTER_METHODDEF
@@ -2661,7 +2691,7 @@ _select_exec(PyObject *m)
26612691
ADD_INT(PIPE_BUF);
26622692
#endif
26632693

2664-
#if defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)
2694+
#if (defined(HAVE_POLL) || defined(HAVE_PPOLL)) && !defined(HAVE_BROKEN_POLL)
26652695
#ifdef __APPLE__
26662696
if (select_have_broken_poll()) {
26672697
if (PyObject_DelAttrString(m, "poll") == -1) {

configure

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

configure.ac

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5253,7 +5253,7 @@ AC_CHECK_FUNCS([ \
52535253
getspnam getuid getwd grantpt if_nameindex initgroups kill killpg lchown linkat \
52545254
lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \
52555255
mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \
5256-
pipe2 plock poll posix_fadvise posix_fallocate posix_openpt posix_spawn posix_spawnp \
5256+
pipe2 plock poll ppoll posix_fadvise posix_fallocate posix_openpt posix_spawn posix_spawnp \
52575257
posix_spawn_file_actions_addclosefrom_np \
52585258
pread preadv preadv2 process_vm_readv \
52595259
pthread_cond_timedwait_relative_np pthread_condattr_setclock pthread_init \

pyconfig.h.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -979,6 +979,9 @@
979979
function. */
980980
#undef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP
981981

982+
/* Define to 1 if you have the 'ppoll' function. */
983+
#undef HAVE_PPOLL
984+
982985
/* Define to 1 if you have the 'pread' function. */
983986
#undef HAVE_PREAD
984987

0 commit comments

Comments
 (0)