Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Doc/library/select.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
12 changes: 10 additions & 2 deletions Lib/test/test_poll.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 6 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
50 changes: 25 additions & 25 deletions Modules/clinic/selectmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 39 additions & 9 deletions Modules/selectmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}
}

Expand All @@ -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) {
Expand All @@ -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)
Expand All @@ -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);

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
6 changes: 6 additions & 0 deletions configure

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
3 changes: 3 additions & 0 deletions pyconfig.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading