[Python-checkins] cpython (merge default -> default): Merge heads

serhiy.storchaka python-checkins at python.org
Tue Mar 31 12:19:19 CEST 2015


https://hg.python.org/cpython/rev/475461033bb2
changeset:   95318:475461033bb2
parent:      95317:5980e81219ed
parent:      95315:76d297869859
user:        Serhiy Storchaka <storchaka at gmail.com>
date:        Tue Mar 31 13:18:35 2015 +0300
summary:
  Merge heads

files:
  Doc/library/select.rst             |   12 +
  Doc/library/selectors.rst          |    6 +
  Doc/whatsnew/3.5.rst               |    5 +-
  Lib/selectors.py                   |   13 +-
  Lib/test/eintrdata/eintr_tester.py |   26 ++-
  Lib/test/test_selectors.py         |   37 +++-
  Modules/selectmodule.c             |  151 ++++++++++------
  7 files changed, 181 insertions(+), 69 deletions(-)


diff --git a/Doc/library/select.rst b/Doc/library/select.rst
--- a/Doc/library/select.rst
+++ b/Doc/library/select.rst
@@ -249,6 +249,12 @@
    returning. If *timeout* is omitted, -1, or :const:`None`, the call will
    block until there is an event for this poll object.
 
+   .. 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`.
+
 
 .. _epoll-objects:
 
@@ -454,6 +460,12 @@
    - 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/library/selectors.rst b/Doc/library/selectors.rst
--- a/Doc/library/selectors.rst
+++ b/Doc/library/selectors.rst
@@ -159,6 +159,12 @@
           timeout has elapsed if the current process receives a signal: in this
           case, an empty list will be returned.
 
+      .. versionchanged:: 3.5
+         The selector is now retried with a recomputed timeout when interrupted
+         by a signal if the signal handler did not raise an exception (see
+         :pep:`475` for the rationale), instead of returning an empty list
+         of events before the timeout.
+
    .. method:: close()
 
       Close the selector.
diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst
--- a/Doc/whatsnew/3.5.rst
+++ b/Doc/whatsnew/3.5.rst
@@ -619,9 +619,10 @@
   instead of raising :exc:`InterruptedError` if the signal handler does not
   raise an exception:
 
-  - :func:`os.open`, :func:`open`
+  - :func:`open`, :func:`os.open`, :func:`io.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:`select.devpoll.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
--- a/Lib/selectors.py
+++ b/Lib/selectors.py
@@ -479,11 +479,10 @@
                 # devpoll() has a resolution of 1 millisecond, round away from
                 # zero to wait *at least* timeout seconds.
                 timeout = math.ceil(timeout * 1e3)
+
+            fd_event_list = self._devpoll.poll(timeout)
+
             ready = []
-            try:
-                fd_event_list = self._devpoll.poll(timeout)
-            except InterruptedError:
-                return ready
             for fd, event in fd_event_list:
                 events = 0
                 if event & ~select.POLLIN:
@@ -549,11 +548,9 @@
         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
--- a/Lib/test/eintrdata/eintr_tester.py
+++ b/Lib/test/eintrdata/eintr_tester.py
@@ -315,8 +315,8 @@
     def test_select(self):
         t0 = time.monotonic()
         select.select([], [], [], self.sleep_time)
+        dt = time.monotonic() - t0
         self.stop_alarm()
-        dt = time.monotonic() - t0
         self.assertGreaterEqual(dt, self.sleep_time)
 
     @unittest.skipUnless(hasattr(select, 'poll'), 'need select.poll')
@@ -325,8 +325,8 @@
 
         t0 = time.monotonic()
         poller.poll(self.sleep_time * 1e3)
+        dt = time.monotonic() - t0
         self.stop_alarm()
-        dt = time.monotonic() - t0
         self.assertGreaterEqual(dt, self.sleep_time)
 
     @unittest.skipUnless(hasattr(select, 'epoll'), 'need select.epoll')
@@ -336,8 +336,30 @@
 
         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)
+
+    @unittest.skipUnless(hasattr(select, 'devpoll'), 'need select.devpoll')
+    def test_devpoll(self):
+        poller = select.devpoll()
+        self.addCleanup(poller.close)
+
+        t0 = time.monotonic()
+        poller.poll(self.sleep_time * 1e3)
+        dt = time.monotonic() - t0
+        self.stop_alarm()
         self.assertGreaterEqual(dt, self.sleep_time)
 
 
diff --git a/Lib/test/test_selectors.py b/Lib/test/test_selectors.py
--- a/Lib/test/test_selectors.py
+++ b/Lib/test/test_selectors.py
@@ -357,7 +357,35 @@
 
     @unittest.skipUnless(hasattr(signal, "alarm"),
                          "signal.alarm() required for this test")
-    def test_select_interrupt(self):
+    def test_select_interrupt_exc(self):
+        s = self.SELECTOR()
+        self.addCleanup(s.close)
+
+        rd, wr = self.make_socketpair()
+
+        class InterruptSelect(Exception):
+            pass
+
+        def handler(*args):
+            raise InterruptSelect
+
+        orig_alrm_handler = signal.signal(signal.SIGALRM, handler)
+        self.addCleanup(signal.signal, signal.SIGALRM, orig_alrm_handler)
+        self.addCleanup(signal.alarm, 0)
+
+        signal.alarm(1)
+
+        s.register(rd, selectors.EVENT_READ)
+        t = time()
+        # select() is interrupted by a signal which raises an exception
+        with self.assertRaises(InterruptSelect):
+            s.select(30)
+        # select() was interrupted before the timeout of 30 seconds
+        self.assertLess(time() - t, 5.0)
+
+    @unittest.skipUnless(hasattr(signal, "alarm"),
+                         "signal.alarm() required for this test")
+    def test_select_interrupt_noraise(self):
         s = self.SELECTOR()
         self.addCleanup(s.close)
 
@@ -371,8 +399,11 @@
 
         s.register(rd, selectors.EVENT_READ)
         t = time()
-        self.assertFalse(s.select(2))
-        self.assertLess(time() - t, 2.5)
+        # select() is interrupted by a signal, but the signal handler doesn't
+        # raise an exception, so select() should by retries with a recomputed
+        # timeout
+        self.assertFalse(s.select(1.5))
+        self.assertGreaterEqual(time() - t, 1.0)
 
 
 class ScalableSelectorMixIn:
diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c
--- a/Modules/selectmodule.c
+++ b/Modules/selectmodule.c
@@ -876,40 +876,38 @@
 devpoll_poll(devpollObject *self, PyObject *args)
 {
     struct dvpoll dvp;
-    PyObject *result_list = NULL, *tout = NULL;
+    PyObject *result_list = NULL, *timeout_obj = NULL;
     int poll_result, i;
-    long timeout;
     PyObject *value, *num1, *num2;
+    _PyTime_t timeout, ms, deadline = 0;
 
     if (self->fd_devpoll < 0)
         return devpoll_err_closed();
 
-    if (!PyArg_UnpackTuple(args, "poll", 0, 1, &tout)) {
+    if (!PyArg_ParseTuple(args, "|O:poll", &timeout_obj)) {
         return NULL;
     }
 
     /* Check values for timeout */
-    if (tout == NULL || tout == Py_None)
+    if (timeout_obj == NULL || timeout_obj == Py_None) {
         timeout = -1;
-    else if (!PyNumber_Check(tout)) {
-        PyErr_SetString(PyExc_TypeError,
-                        "timeout must be an integer or None");
-        return NULL;
+        ms = -1;
     }
     else {
-        tout = PyNumber_Long(tout);
-        if (!tout)
+        if (_PyTime_FromMillisecondsObject(&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;
-        timeout = PyLong_AsLong(tout);
-        Py_DECREF(tout);
-        if (timeout == -1 && PyErr_Occurred())
+        }
+
+        ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_CEILING);
+        if (ms < -1 || ms > INT_MAX) {
+            PyErr_SetString(PyExc_OverflowError, "timeout is too large");
             return NULL;
-    }
-
-    if ((timeout < -1) || (timeout > INT_MAX)) {
-        PyErr_SetString(PyExc_OverflowError,
-                        "timeout is out of range");
-        return NULL;
+        }
     }
 
     if (devpoll_flush(self))
@@ -917,12 +915,36 @@
 
     dvp.dp_fds = self->fds;
     dvp.dp_nfds = self->max_n_fds;
-    dvp.dp_timeout = timeout;
+    dvp.dp_timeout = (int)ms;
 
-    /* call devpoll() */
-    Py_BEGIN_ALLOW_THREADS
-    poll_result = ioctl(self->fd_devpoll, DP_POLL, &dvp);
-    Py_END_ALLOW_THREADS
+    if (timeout >= 0)
+        deadline = _PyTime_GetMonotonicClock() + timeout;
+
+    do {
+        /* call devpoll() */
+        Py_BEGIN_ALLOW_THREADS
+        errno = 0;
+        poll_result = ioctl(self->fd_devpoll, DP_POLL, &dvp);
+        Py_END_ALLOW_THREADS
+
+        if (errno != EINTR)
+            break;
+
+        /* devpoll() was interrupted by a signal */
+        if (PyErr_CheckSignals())
+            return NULL;
+
+        if (timeout >= 0) {
+            timeout = deadline - _PyTime_GetMonotonicClock();
+            if (timeout < 0) {
+                poll_result = 0;
+                break;
+            }
+            ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_CEILING);
+            dvp.dp_timeout = (int)ms;
+            /* retry devpoll() with the recomputed timeout */
+        }
+    } while (1);
 
     if (poll_result < 0) {
         PyErr_SetFromErrno(PyExc_IOError);
@@ -930,28 +952,26 @@
     }
 
     /* build the result list */
-
     result_list = PyList_New(poll_result);
     if (!result_list)
         return NULL;
-    else {
-        for (i = 0; i < poll_result; i++) {
-            num1 = PyLong_FromLong(self->fds[i].fd);
-            num2 = PyLong_FromLong(self->fds[i].revents);
-            if ((num1 == NULL) || (num2 == NULL)) {
-                Py_XDECREF(num1);
-                Py_XDECREF(num2);
-                goto error;
-            }
-            value = PyTuple_Pack(2, num1, num2);
-            Py_DECREF(num1);
-            Py_DECREF(num2);
-            if (value == NULL)
-                goto error;
-            if ((PyList_SetItem(result_list, i, value)) == -1) {
-                Py_DECREF(value);
-                goto error;
-            }
+
+    for (i = 0; i < poll_result; i++) {
+        num1 = PyLong_FromLong(self->fds[i].fd);
+        num2 = PyLong_FromLong(self->fds[i].revents);
+        if ((num1 == NULL) || (num2 == NULL)) {
+            Py_XDECREF(num1);
+            Py_XDECREF(num2);
+            goto error;
+        }
+        value = PyTuple_Pack(2, num1, num2);
+        Py_DECREF(num1);
+        Py_DECREF(num2);
+        if (value == NULL)
+            goto error;
+        if ((PyList_SetItem(result_list, i, value)) == -1) {
+            Py_DECREF(value);
+            goto error;
         }
     }
 
@@ -2083,8 +2103,9 @@
     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 +2124,7 @@
         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 +2133,15 @@
             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 +2186,34 @@
         }
     }
 
-    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);

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list