From 4448c08451e947ccf197742d22fa49adba71e363 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 31 Mar 2015 11:48:34 +0200 Subject: Issue #23485: select.kqueue.control() is now retried when interrupted by a signal --- Doc/library/select.rst | 6 +++++ Doc/whatsnew/3.5.rst | 3 ++- Lib/selectors.py | 6 ++--- Lib/test/eintrdata/eintr_tester.py | 15 +++++++++++-- Modules/selectmodule.c | 45 ++++++++++++++++++++++++++++---------- 5 files changed, 57 insertions(+), 18 deletions(-) diff --git a/Doc/library/select.rst b/Doc/library/select.rst index 61f3835..e6f95fd 100644 --- a/Doc/library/select.rst +++ b/Doc/library/select.rst @@ -454,6 +454,12 @@ Kqueue Objects - max_events must be 0 or a positive integer - timeout in seconds (floats possible) + .. 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`. + .. _kevent-objects: diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst index 6c2d521..e5fefae 100644 --- a/Doc/whatsnew/3.5.rst +++ b/Doc/whatsnew/3.5.rst @@ -621,7 +621,8 @@ 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.epoll.poll` + - :func:`select.select`, :func:`select.poll.poll`, :func:`select.epoll.poll`, + :func:`select.kqueue.control` - :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 2a0a44c..a3f2e78 100644 --- a/Lib/selectors.py +++ b/Lib/selectors.py @@ -549,11 +549,9 @@ if hasattr(select, 'kqueue'): def select(self, timeout=None): timeout = None if timeout is None else max(timeout, 0) max_ev = len(self._fd_to_key) + kev_list = self._kqueue.control(None, max_ev, timeout) + ready = [] - try: - kev_list = self._kqueue.control(None, max_ev, timeout) - except InterruptedError: - return ready for kev in kev_list: fd = kev.ident flag = kev.filter diff --git a/Lib/test/eintrdata/eintr_tester.py b/Lib/test/eintrdata/eintr_tester.py index 0df9762..4e9b992 100644 --- a/Lib/test/eintrdata/eintr_tester.py +++ b/Lib/test/eintrdata/eintr_tester.py @@ -315,8 +315,8 @@ class SelectEINTRTest(EINTRBaseTest): def test_select(self): t0 = time.monotonic() select.select([], [], [], self.sleep_time) - self.stop_alarm() dt = time.monotonic() - t0 + self.stop_alarm() self.assertGreaterEqual(dt, self.sleep_time) @unittest.skipUnless(hasattr(select, 'poll'), 'need select.poll') @@ -325,8 +325,8 @@ class SelectEINTRTest(EINTRBaseTest): t0 = time.monotonic() poller.poll(self.sleep_time * 1e3) - self.stop_alarm() dt = time.monotonic() - t0 + self.stop_alarm() self.assertGreaterEqual(dt, self.sleep_time) @unittest.skipUnless(hasattr(select, 'epoll'), 'need select.epoll') @@ -336,8 +336,19 @@ class SelectEINTRTest(EINTRBaseTest): t0 = time.monotonic() poller.poll(self.sleep_time) + dt = time.monotonic() - t0 self.stop_alarm() + self.assertGreaterEqual(dt, self.sleep_time) + + @unittest.skipUnless(hasattr(select, 'kqueue'), 'need select.kqueue') + def test_kqueue(self): + kqueue = select.kqueue() + self.addCleanup(kqueue.close) + + t0 = time.monotonic() + kqueue.control(None, 1, self.sleep_time) dt = time.monotonic() - t0 + self.stop_alarm() self.assertGreaterEqual(dt, self.sleep_time) diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c index 96be5ba..452525d 100644 --- a/Modules/selectmodule.c +++ b/Modules/selectmodule.c @@ -2083,8 +2083,9 @@ kqueue_queue_control(kqueue_queue_Object *self, PyObject *args) PyObject *result = NULL; struct kevent *evl = NULL; struct kevent *chl = NULL; - struct timespec timeout; + struct timespec timeoutspec; struct timespec *ptimeoutspec; + _PyTime_t timeout, deadline = 0; if (self->kqfd < 0) return kqueue_queue_err_closed(); @@ -2103,9 +2104,7 @@ kqueue_queue_control(kqueue_queue_Object *self, PyObject *args) ptimeoutspec = NULL; } else { - _PyTime_t ts; - - if (_PyTime_FromSecondsObject(&ts, + if (_PyTime_FromSecondsObject(&timeout, otimeout, _PyTime_ROUND_CEILING) < 0) { PyErr_Format(PyExc_TypeError, "timeout argument must be an number " @@ -2114,15 +2113,15 @@ kqueue_queue_control(kqueue_queue_Object *self, PyObject *args) return NULL; } - if (_PyTime_AsTimespec(ts, &timeout) == -1) + if (_PyTime_AsTimespec(timeout, &timeoutspec) == -1) return NULL; - if (timeout.tv_sec < 0) { + if (timeoutspec.tv_sec < 0) { PyErr_SetString(PyExc_ValueError, "timeout must be positive or None"); return NULL; } - ptimeoutspec = &timeout; + ptimeoutspec = &timeoutspec; } if (ch != NULL && ch != Py_None) { @@ -2167,10 +2166,34 @@ kqueue_queue_control(kqueue_queue_Object *self, PyObject *args) } } - Py_BEGIN_ALLOW_THREADS - gotevents = kevent(self->kqfd, chl, nchanges, - evl, nevents, ptimeoutspec); - Py_END_ALLOW_THREADS + if (ptimeoutspec) + deadline = _PyTime_GetMonotonicClock() + timeout; + + do { + Py_BEGIN_ALLOW_THREADS + errno = 0; + gotevents = kevent(self->kqfd, chl, nchanges, + evl, nevents, ptimeoutspec); + Py_END_ALLOW_THREADS + + if (errno != EINTR) + break; + + /* kevent() was interrupted by a signal */ + if (PyErr_CheckSignals()) + goto error; + + if (ptimeoutspec) { + timeout = deadline - _PyTime_GetMonotonicClock(); + if (timeout < 0) { + gotevents = 0; + break; + } + if (_PyTime_AsTimespec(timeout, &timeoutspec) == -1) + goto error; + /* retry kevent() with the recomputed timeout */ + } + } while (1); if (gotevents == -1) { PyErr_SetFromErrno(PyExc_OSError); -- cgit v0.12