[Python-checkins] cpython: Issue #23485: select.poll.poll() is now retried when interrupted by a signal

victor.stinner python-checkins at python.org
Mon Mar 30 22:15:59 CEST 2015


https://hg.python.org/cpython/rev/69b1683ee001
changeset:   95307:69b1683ee001
user:        Victor Stinner <victor.stinner at gmail.com>
date:        Mon Mar 30 21:38:00 2015 +0200
summary:
  Issue #23485: select.poll.poll() is now retried when interrupted by a signal

files:
  Doc/library/select.rst             |    6 +
  Doc/whatsnew/3.5.rst               |    2 +-
  Lib/asyncore.py                    |    6 +-
  Lib/selectors.py                   |    7 +-
  Lib/test/eintrdata/eintr_tester.py |   20 ++-
  Modules/selectmodule.c             |  131 ++++++++++------
  6 files changed, 111 insertions(+), 61 deletions(-)


diff --git a/Doc/library/select.rst b/Doc/library/select.rst
--- a/Doc/library/select.rst
+++ b/Doc/library/select.rst
@@ -408,6 +408,12 @@
    returning. If *timeout* is omitted, negative, 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`.
+
 
 .. _kqueue-objects:
 
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
@@ -621,7 +621,7 @@
 
   - :func:`os.open`, :func:`open`
   - :func:`os.read`, :func:`os.write`
-  - :func:`select.select`
+  - :func:`select.select`, :func:`select.poll.poll`
   - :func:`time.sleep`
 
 * Before Python 3.5, a :class:`datetime.time` object was considered to be false
diff --git a/Lib/asyncore.py b/Lib/asyncore.py
--- a/Lib/asyncore.py
+++ b/Lib/asyncore.py
@@ -179,10 +179,8 @@
                 flags |= select.POLLOUT
             if flags:
                 pollster.register(fd, flags)
-        try:
-            r = pollster.poll(timeout)
-        except InterruptedError:
-            r = []
+
+        r = pollster.poll(timeout)
         for fd, flags in r:
             obj = map.get(fd)
             if obj is None:
diff --git a/Lib/selectors.py b/Lib/selectors.py
--- a/Lib/selectors.py
+++ b/Lib/selectors.py
@@ -359,11 +359,10 @@
                 # poll() 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._poll.poll(timeout)
+
             ready = []
-            try:
-                fd_event_list = self._poll.poll(timeout)
-            except InterruptedError:
-                return ready
             for fd, event in fd_event_list:
                 events = 0
                 if event & ~select.POLLIN:
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
@@ -38,8 +38,12 @@
                          cls.signal_period)
 
     @classmethod
+    def stop_alarm(cls):
+        signal.setitimer(signal.ITIMER_REAL, 0, 0)
+
+    @classmethod
     def tearDownClass(cls):
-        signal.setitimer(signal.ITIMER_REAL, 0, 0)
+        cls.stop_alarm()
         signal.signal(signal.SIGALRM, cls.orig_handler)
 
     @classmethod
@@ -260,7 +264,7 @@
     def test_sleep(self):
         t0 = time.monotonic()
         time.sleep(self.sleep_time)
-        signal.alarm(0)
+        self.stop_alarm()
         dt = time.monotonic() - t0
         self.assertGreaterEqual(dt, self.sleep_time)
 
@@ -311,7 +315,17 @@
     def test_select(self):
         t0 = time.monotonic()
         select.select([], [], [], self.sleep_time)
-        signal.alarm(0)
+        self.stop_alarm()
+        dt = time.monotonic() - t0
+        self.assertGreaterEqual(dt, self.sleep_time)
+
+    @unittest.skipUnless(hasattr(select, 'poll'), 'need select.poll')
+    def test_poll(self):
+        poller = select.poll()
+
+        t0 = time.monotonic()
+        poller.poll(self.sleep_time * 1e3)
+        self.stop_alarm()
         dt = time.monotonic() - t0
         self.assertGreaterEqual(dt, self.sleep_time)
 
diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c
--- a/Modules/selectmodule.c
+++ b/Modules/selectmodule.c
@@ -279,6 +279,7 @@
                 break;
             }
             _PyTime_AsTimeval_noraise(timeout, &tv, _PyTime_ROUND_CEILING);
+            /* retry select() with the recomputed timeout */
         }
     } while (1);
 
@@ -520,30 +521,39 @@
 static PyObject *
 poll_poll(pollObject *self, PyObject *args)
 {
-    PyObject *result_list = NULL, *tout = NULL;
-    int timeout = 0, poll_result, i, j;
+    PyObject *result_list = NULL, *timeout_obj = NULL;
+    int poll_result, i, j;
     PyObject *value = NULL, *num = NULL;
+    _PyTime_t timeout, ms, deadline;
+    int async_err = 0;
 
-    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;
+        deadline = 0;
     }
     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_AsInt(tout);
-        Py_DECREF(tout);
-        if (timeout == -1 && PyErr_Occurred())
+        }
+
+        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;
     }
 
     /* Avoid concurrent poll() invocation, issue 8865 */
@@ -561,14 +571,38 @@
     self->poll_running = 1;
 
     /* call poll() */
-    Py_BEGIN_ALLOW_THREADS
-    poll_result = poll(self->ufds, self->ufd_len, timeout);
-    Py_END_ALLOW_THREADS
+    async_err = 0;
+    do {
+        Py_BEGIN_ALLOW_THREADS
+        errno = 0;
+        poll_result = poll(self->ufds, self->ufd_len, (int)ms);
+        Py_END_ALLOW_THREADS
+
+        if (errno != EINTR)
+            break;
+
+        /* poll() was interrupted by a signal */
+        if (PyErr_CheckSignals()) {
+            async_err = 1;
+            break;
+        }
+
+        if (timeout >= 0) {
+            timeout = deadline - _PyTime_GetMonotonicClock();
+            if (timeout < 0) {
+                poll_result = 0;
+                break;
+            }
+            ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_CEILING);
+            /* retry poll() with the recomputed timeout */
+        }
+    } while (1);
 
     self->poll_running = 0;
 
     if (poll_result < 0) {
-        PyErr_SetFromErrno(PyExc_OSError);
+        if (!async_err)
+            PyErr_SetFromErrno(PyExc_OSError);
         return NULL;
     }
 
@@ -577,41 +611,40 @@
     result_list = PyList_New(poll_result);
     if (!result_list)
         return NULL;
-    else {
-        for (i = 0, j = 0; j < poll_result; j++) {
-            /* skip to the next fired descriptor */
-            while (!self->ufds[i].revents) {
-                i++;
-            }
-            /* if we hit a NULL return, set value to NULL
-               and break out of loop; code at end will
-               clean up result_list */
-            value = PyTuple_New(2);
-            if (value == NULL)
-                goto error;
-            num = PyLong_FromLong(self->ufds[i].fd);
-            if (num == NULL) {
-                Py_DECREF(value);
-                goto error;
-            }
-            PyTuple_SET_ITEM(value, 0, num);
 
-            /* The &0xffff is a workaround for AIX.  'revents'
-               is a 16-bit short, and IBM assigned POLLNVAL
-               to be 0x8000, so the conversion to int results
-               in a negative number. See SF bug #923315. */
-            num = PyLong_FromLong(self->ufds[i].revents & 0xffff);
-            if (num == NULL) {
-                Py_DECREF(value);
-                goto error;
-            }
-            PyTuple_SET_ITEM(value, 1, num);
-            if ((PyList_SetItem(result_list, j, value)) == -1) {
-                Py_DECREF(value);
-                goto error;
-            }
+    for (i = 0, j = 0; j < poll_result; j++) {
+        /* skip to the next fired descriptor */
+        while (!self->ufds[i].revents) {
             i++;
         }
+        /* if we hit a NULL return, set value to NULL
+           and break out of loop; code at end will
+           clean up result_list */
+        value = PyTuple_New(2);
+        if (value == NULL)
+            goto error;
+        num = PyLong_FromLong(self->ufds[i].fd);
+        if (num == NULL) {
+            Py_DECREF(value);
+            goto error;
+        }
+        PyTuple_SET_ITEM(value, 0, num);
+
+        /* The &0xffff is a workaround for AIX.  'revents'
+           is a 16-bit short, and IBM assigned POLLNVAL
+           to be 0x8000, so the conversion to int results
+           in a negative number. See SF bug #923315. */
+        num = PyLong_FromLong(self->ufds[i].revents & 0xffff);
+        if (num == NULL) {
+            Py_DECREF(value);
+            goto error;
+        }
+        PyTuple_SET_ITEM(value, 1, num);
+        if ((PyList_SetItem(result_list, j, value)) == -1) {
+            Py_DECREF(value);
+            goto error;
+        }
+        i++;
     }
     return result_list;
 

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


More information about the Python-checkins mailing list