From 41eba224de4d4752346ab051b6a411ec6f4a29fb Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 30 Mar 2015 21:59:21 +0200 Subject: [PATCH] Issue #23485: select.epoll.poll() is now retried when interrupted by a signal --- Doc/library/select.rst | 6 +++ Doc/whatsnew/3.5.rst | 2 +- Lib/selectors.py | 6 +-- Lib/test/eintrdata/eintr_tester.py | 11 +++++ Modules/selectmodule.c | 69 ++++++++++++++++++++++-------- 5 files changed, 71 insertions(+), 23 deletions(-) diff --git a/Doc/library/select.rst b/Doc/library/select.rst index 26e74c70b7c..61f3835a6c7 100644 --- a/Doc/library/select.rst +++ b/Doc/library/select.rst @@ -329,6 +329,12 @@ Edge and Level Trigger Polling (epoll) Objects 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: diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst index 07776c11b03..a0d1bd94ca6 100644 --- a/Doc/whatsnew/3.5.rst +++ b/Doc/whatsnew/3.5.rst @@ -621,7 +621,7 @@ Changes in the Python API - :func:`os.open`, :func:`open` - :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` * Before Python 3.5, a :class:`datetime.time` object was considered to be false diff --git a/Lib/selectors.py b/Lib/selectors.py index bf48ebf040b..2a0a44c9248 100644 --- a/Lib/selectors.py +++ b/Lib/selectors.py @@ -423,11 +423,9 @@ if hasattr(select, 'epoll'): # FD is registered. max_ev = max(len(self._fd_to_key), 1) + fd_event_list = self._epoll.poll(timeout, max_ev) + ready = [] - try: - fd_event_list = self._epoll.poll(timeout, max_ev) - except InterruptedError: - return ready for fd, event in fd_event_list: events = 0 if event & ~select.EPOLLIN: diff --git a/Lib/test/eintrdata/eintr_tester.py b/Lib/test/eintrdata/eintr_tester.py index 3da964d99b3..0df9762cfc1 100644 --- a/Lib/test/eintrdata/eintr_tester.py +++ b/Lib/test/eintrdata/eintr_tester.py @@ -329,6 +329,17 @@ class SelectEINTRTest(EINTRBaseTest): dt = time.monotonic() - t0 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(): support.run_unittest( diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c index 71b66836602..96be5ba40f3 100644 --- a/Modules/selectmodule.c +++ b/Modules/selectmodule.c @@ -535,7 +535,7 @@ poll_poll(pollObject *self, PyObject *args) if (timeout_obj == NULL || timeout_obj == Py_None) { timeout = -1; ms = -1; - deadline = 0; + deadline = 0; /* initialize to prevent gcc warning */ } else { if (_PyTime_FromMillisecondsObject(&timeout, timeout_obj, @@ -1465,34 +1465,46 @@ fd is the target file descriptor of the operation."); static PyObject * pyepoll_poll(pyEpoll_Object *self, PyObject *args, PyObject *kwds) { - double dtimeout = -1.; - int timeout; + static char *kwlist[] = {"timeout", "maxevents", NULL}; + PyObject *timeout_obj = NULL; int maxevents = -1; int nfds, i; PyObject *elist = NULL, *etuple = NULL; struct epoll_event *evs = NULL; - static char *kwlist[] = {"timeout", "maxevents", NULL}; + _PyTime_t timeout, ms, deadline; if (self->epfd < 0) return pyepoll_err_closed(); - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|di:poll", kwlist, - &dtimeout, &maxevents)) { + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oi:poll", kwlist, + &timeout_obj, &maxevents)) { return NULL; } - if (dtimeout < 0) { + if (timeout_obj == NULL || timeout_obj == Py_None) { timeout = -1; - } - else if (dtimeout * 1000.0 > INT_MAX) { - PyErr_SetString(PyExc_OverflowError, - "timeout is too large"); - return NULL; + ms = -1; + deadline = 0; /* initialize to prevent gcc warning */ } else { - /* epoll_wait() has a resolution of 1 millisecond, round away from zero - to wait *at least* dtimeout seconds. */ - timeout = (int)ceil(dtimeout * 1000.0); + /* epoll_wait() has a resolution of 1 millisecond, round towards + infinity to wait at least timeout seconds. */ + 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) { @@ -1511,9 +1523,30 @@ pyepoll_poll(pyEpoll_Object *self, PyObject *args, PyObject *kwds) return NULL; } - Py_BEGIN_ALLOW_THREADS - nfds = epoll_wait(self->epfd, evs, maxevents, timeout); - Py_END_ALLOW_THREADS + do { + Py_BEGIN_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) { PyErr_SetFromErrno(PyExc_OSError); goto error;