Issue #23485: select.epoll.poll() is now retried when interrupted by a signal

This commit is contained in:
Victor Stinner 2015-03-30 21:59:21 +02:00
parent 3c7d6e0693
commit 41eba224de
5 changed files with 71 additions and 23 deletions

View File

@ -329,6 +329,12 @@ Edge and Level Trigger Polling (epoll) Objects
Wait for events. timeout in seconds (float) Wait for events. timeout in seconds (float)
.. versionchanged:: 3.5
The function is now retried with a recomputed timeout when interrupted by
a signal, except if the signal handler raises an exception (see
:pep:`475` for the rationale), instead of raising
:exc:`InterruptedError`.
.. _poll-objects: .. _poll-objects:

View File

@ -621,7 +621,7 @@ Changes in the Python API
- :func:`os.open`, :func:`open` - :func:`os.open`, :func:`open`
- :func:`os.read`, :func:`os.write` - :func:`os.read`, :func:`os.write`
- :func:`select.select`, :func:`select.poll.poll` - :func:`select.select`, :func:`select.poll.poll`, :func:`select.epoll.poll`
- :func:`time.sleep` - :func:`time.sleep`
* Before Python 3.5, a :class:`datetime.time` object was considered to be false * Before Python 3.5, a :class:`datetime.time` object was considered to be false

View File

@ -423,11 +423,9 @@ if hasattr(select, 'epoll'):
# FD is registered. # FD is registered.
max_ev = max(len(self._fd_to_key), 1) max_ev = max(len(self._fd_to_key), 1)
fd_event_list = self._epoll.poll(timeout, max_ev)
ready = [] ready = []
try:
fd_event_list = self._epoll.poll(timeout, max_ev)
except InterruptedError:
return ready
for fd, event in fd_event_list: for fd, event in fd_event_list:
events = 0 events = 0
if event & ~select.EPOLLIN: if event & ~select.EPOLLIN:

View File

@ -329,6 +329,17 @@ class SelectEINTRTest(EINTRBaseTest):
dt = time.monotonic() - t0 dt = time.monotonic() - t0
self.assertGreaterEqual(dt, self.sleep_time) self.assertGreaterEqual(dt, self.sleep_time)
@unittest.skipUnless(hasattr(select, 'epoll'), 'need select.epoll')
def test_epoll(self):
poller = select.epoll()
self.addCleanup(poller.close)
t0 = time.monotonic()
poller.poll(self.sleep_time)
self.stop_alarm()
dt = time.monotonic() - t0
self.assertGreaterEqual(dt, self.sleep_time)
def test_main(): def test_main():
support.run_unittest( support.run_unittest(

View File

@ -535,7 +535,7 @@ poll_poll(pollObject *self, PyObject *args)
if (timeout_obj == NULL || timeout_obj == Py_None) { if (timeout_obj == NULL || timeout_obj == Py_None) {
timeout = -1; timeout = -1;
ms = -1; ms = -1;
deadline = 0; deadline = 0; /* initialize to prevent gcc warning */
} }
else { else {
if (_PyTime_FromMillisecondsObject(&timeout, timeout_obj, if (_PyTime_FromMillisecondsObject(&timeout, timeout_obj,
@ -1465,34 +1465,46 @@ fd is the target file descriptor of the operation.");
static PyObject * static PyObject *
pyepoll_poll(pyEpoll_Object *self, PyObject *args, PyObject *kwds) pyepoll_poll(pyEpoll_Object *self, PyObject *args, PyObject *kwds)
{ {
double dtimeout = -1.; static char *kwlist[] = {"timeout", "maxevents", NULL};
int timeout; PyObject *timeout_obj = NULL;
int maxevents = -1; int maxevents = -1;
int nfds, i; int nfds, i;
PyObject *elist = NULL, *etuple = NULL; PyObject *elist = NULL, *etuple = NULL;
struct epoll_event *evs = NULL; struct epoll_event *evs = NULL;
static char *kwlist[] = {"timeout", "maxevents", NULL}; _PyTime_t timeout, ms, deadline;
if (self->epfd < 0) if (self->epfd < 0)
return pyepoll_err_closed(); return pyepoll_err_closed();
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|di:poll", kwlist, if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oi:poll", kwlist,
&dtimeout, &maxevents)) { &timeout_obj, &maxevents)) {
return NULL; return NULL;
} }
if (dtimeout < 0) { if (timeout_obj == NULL || timeout_obj == Py_None) {
timeout = -1; timeout = -1;
} ms = -1;
else if (dtimeout * 1000.0 > INT_MAX) { deadline = 0; /* initialize to prevent gcc warning */
PyErr_SetString(PyExc_OverflowError,
"timeout is too large");
return NULL;
} }
else { else {
/* epoll_wait() has a resolution of 1 millisecond, round away from zero /* epoll_wait() has a resolution of 1 millisecond, round towards
to wait *at least* dtimeout seconds. */ infinity to wait at least timeout seconds. */
timeout = (int)ceil(dtimeout * 1000.0); if (_PyTime_FromSecondsObject(&timeout, timeout_obj,
_PyTime_ROUND_CEILING) < 0) {
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
PyErr_SetString(PyExc_TypeError,
"timeout must be an integer or None");
}
return NULL;
}
ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_CEILING);
if (ms < INT_MIN || ms > INT_MAX) {
PyErr_SetString(PyExc_OverflowError, "timeout is too large");
return NULL;
}
deadline = _PyTime_GetMonotonicClock() + timeout;
} }
if (maxevents == -1) { if (maxevents == -1) {
@ -1511,9 +1523,30 @@ pyepoll_poll(pyEpoll_Object *self, PyObject *args, PyObject *kwds)
return NULL; return NULL;
} }
Py_BEGIN_ALLOW_THREADS do {
nfds = epoll_wait(self->epfd, evs, maxevents, timeout); Py_BEGIN_ALLOW_THREADS
Py_END_ALLOW_THREADS errno = 0;
nfds = epoll_wait(self->epfd, evs, maxevents, (int)ms);
Py_END_ALLOW_THREADS
if (errno != EINTR)
break;
/* poll() was interrupted by a signal */
if (PyErr_CheckSignals())
goto error;
if (timeout >= 0) {
timeout = deadline - _PyTime_GetMonotonicClock();
if (timeout < 0) {
nfds = 0;
break;
}
ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_CEILING);
/* retry epoll_wait() with the recomputed timeout */
}
} while(1);
if (nfds < 0) { if (nfds < 0) {
PyErr_SetFromErrno(PyExc_OSError); PyErr_SetFromErrno(PyExc_OSError);
goto error; goto error;