[Python-checkins] cpython: Issue #23485: select.select() is now retried automatically with the recomputed

victor.stinner python-checkins at python.org
Mon Mar 30 21:29:07 CEST 2015


https://hg.python.org/cpython/rev/0ff1090307c7
changeset:   95302:0ff1090307c7
user:        Victor Stinner <victor.stinner at gmail.com>
date:        Mon Mar 30 21:16:11 2015 +0200
summary:
  Issue #23485: select.select() is now retried automatically with the recomputed
timeout when interrupted by a signal, except if the signal handler raises an
exception. This change is part of the PEP 475.

The asyncore and selectors module doesn't catch the InterruptedError exception
anymore when calling select.select(), since this function should not raise
InterruptedError anymore.

files:
  Doc/library/errno.rst              |   5 +-
  Doc/library/exceptions.rst         |   7 ++-
  Doc/library/select.rst             |   7 ++
  Doc/whatsnew/3.5.rst               |  14 ++--
  Lib/asyncore.py                    |   5 +-
  Lib/selectors.py                   |   5 +-
  Lib/test/eintrdata/eintr_tester.py |  16 +++++-
  Misc/NEWS                          |   4 +
  Modules/selectmodule.c             |  53 +++++++++++++----
  9 files changed, 85 insertions(+), 31 deletions(-)


diff --git a/Doc/library/errno.rst b/Doc/library/errno.rst
--- a/Doc/library/errno.rst
+++ b/Doc/library/errno.rst
@@ -41,7 +41,10 @@
 
 .. data:: EINTR
 
-   Interrupted system call
+   Interrupted system call.
+
+   .. seealso::
+      This error is mapped to the exception :exc:`InterruptedError`.
 
 
 .. data:: EIO
diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst
--- a/Doc/library/exceptions.rst
+++ b/Doc/library/exceptions.rst
@@ -536,7 +536,12 @@
 .. exception:: InterruptedError
 
    Raised when a system call is interrupted by an incoming signal.
-   Corresponds to :c:data:`errno` ``EINTR``.
+   Corresponds to :c:data:`errno` :py:data:`~errno.EINTR`.
+
+   .. versionchanged:: 3.5
+      Python now retries system calls when a syscall is interrupted by a
+      signal, except if the signal handler raises an exception (see :pep:`475`
+      for the rationale), instead of raising :exc:`InterruptedError`.
 
 .. exception:: IsADirectoryError
 
diff --git a/Doc/library/select.rst b/Doc/library/select.rst
--- a/Doc/library/select.rst
+++ b/Doc/library/select.rst
@@ -145,6 +145,13 @@
       library, and does not handle file descriptors that don't originate from
       WinSock.
 
+   .. 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`.
+
+
 .. attribute:: PIPE_BUF
 
    The minimum number of bytes which can be written without blocking to a pipe
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
@@ -173,9 +173,10 @@
 PEP 475: Retry system calls failing with EINTR
 ----------------------------------------------
 
-:pep:`475` adds support for automatic retry of system calls failing with EINTR:
-this means that user code doesn't have to deal with EINTR or InterruptedError
-manually, and should make it more robust against asynchronous signal reception.
+:pep:`475` adds support for automatic retry of system calls failing with
+:py:data:`~errno.EINTR`: this means that user code doesn't have to deal with
+EINTR or :exc:`InterruptedError` manually, and should make it more robust
+against asynchronous signal reception.
 
 .. seealso::
 
@@ -614,12 +615,13 @@
 Changes in the Python API
 -------------------------
 
-* :pep:`475`: the following functions are now retried when interrupted instead
-  of raising :exc:`InterruptedError` if the signal handler does not raise
-  an exception:
+* :pep:`475`: Examples of functions which are now retried when interrupted
+  instead of raising :exc:`InterruptedError` if the signal handler does not
+  raise an exception:
 
   - :func:`os.open`, :func:`open`
   - :func:`os.read`, :func:`os.write`
+  - :func:`select.select`
   - :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
@@ -141,10 +141,7 @@
             time.sleep(timeout)
             return
 
-        try:
-            r, w, e = select.select(r, w, e, timeout)
-        except InterruptedError:
-            return
+        r, w, e = select.select(r, w, e, timeout)
 
         for fd in r:
             obj = map.get(fd)
diff --git a/Lib/selectors.py b/Lib/selectors.py
--- a/Lib/selectors.py
+++ b/Lib/selectors.py
@@ -310,10 +310,7 @@
     def select(self, timeout=None):
         timeout = None if timeout is None else max(timeout, 0)
         ready = []
-        try:
-            r, w, _ = self._select(self._readers, self._writers, [], timeout)
-        except InterruptedError:
-            return ready
+        r, w, _ = self._select(self._readers, self._writers, [], timeout)
         r = set(r)
         w = set(w)
         for fd in r | w:
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
@@ -10,6 +10,7 @@
 
 import io
 import os
+import select
 import signal
 import socket
 import time
@@ -303,12 +304,25 @@
         self.assertGreaterEqual(dt, self.sleep_time)
 
 
+ at unittest.skipUnless(hasattr(signal, "setitimer"), "requires setitimer()")
+class SelectEINTRTest(EINTRBaseTest):
+    """ EINTR tests for the select module. """
+
+    def test_select(self):
+        t0 = time.monotonic()
+        select.select([], [], [], self.sleep_time)
+        signal.alarm(0)
+        dt = time.monotonic() - t0
+        self.assertGreaterEqual(dt, self.sleep_time)
+
+
 def test_main():
     support.run_unittest(
         OSEINTRTest,
         SocketEINTRTest,
         TimeEINTRTest,
-        SignalEINTRTest)
+        SignalEINTRTest,
+        SelectEINTRTest)
 
 
 if __name__ == "__main__":
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -13,6 +13,10 @@
 Library
 -------
 
+- Issue #23485: select.select() is now retried automatically with the
+  recomputed timeout when interrupted by a signal, except if the signal handler
+  raises an exception. This change is part of the PEP 475.
+
 - Issue #23752: When built from an existing file descriptor, io.FileIO() now
   only calls fstat() once. Before fstat() was called twice, which was not
   necessary.
diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c
--- a/Modules/selectmodule.c
+++ b/Modules/selectmodule.c
@@ -193,29 +193,31 @@
 #endif /* SELECT_USES_HEAP */
     PyObject *ifdlist, *ofdlist, *efdlist;
     PyObject *ret = NULL;
-    PyObject *tout = Py_None;
+    PyObject *timeout_obj = Py_None;
     fd_set ifdset, ofdset, efdset;
     struct timeval tv, *tvp;
     int imax, omax, emax, max;
     int n;
+    _PyTime_t timeout, deadline = 0;
 
     /* convert arguments */
     if (!PyArg_UnpackTuple(args, "select", 3, 4,
-                          &ifdlist, &ofdlist, &efdlist, &tout))
+                          &ifdlist, &ofdlist, &efdlist, &timeout_obj))
         return NULL;
 
-    if (tout == Py_None)
-        tvp = (struct timeval *)0;
+    if (timeout_obj == Py_None)
+        tvp = (struct timeval *)NULL;
     else {
-        _PyTime_t ts;
-
-        if (_PyTime_FromSecondsObject(&ts, tout, _PyTime_ROUND_CEILING) < 0) {
-            PyErr_SetString(PyExc_TypeError,
-                            "timeout must be a float or None");
+        if (_PyTime_FromSecondsObject(&timeout, timeout_obj,
+                                      _PyTime_ROUND_CEILING) < 0) {
+            if (PyErr_ExceptionMatches(PyExc_TypeError)) {
+                PyErr_SetString(PyExc_TypeError,
+                                "timeout must be a float or None");
+            }
             return NULL;
         }
 
-        if (_PyTime_AsTimeval(ts, &tv, _PyTime_ROUND_CEILING) == -1)
+        if (_PyTime_AsTimeval(timeout, &tv, _PyTime_ROUND_CEILING) == -1)
             return NULL;
         if (tv.tv_sec < 0) {
             PyErr_SetString(PyExc_ValueError, "timeout must be non-negative");
@@ -224,7 +226,6 @@
         tvp = &tv;
     }
 
-
 #ifdef SELECT_USES_HEAP
     /* Allocate memory for the lists */
     rfd2obj = PyMem_NEW(pylist, FD_SETSIZE + 1);
@@ -237,6 +238,7 @@
         return PyErr_NoMemory();
     }
 #endif /* SELECT_USES_HEAP */
+
     /* Convert sequences to fd_sets, and get maximum fd number
      * propagates the Python exception set in seq2set()
      */
@@ -249,13 +251,36 @@
         goto finally;
     if ((emax=seq2set(efdlist, &efdset, efd2obj)) < 0)
         goto finally;
+
     max = imax;
     if (omax > max) max = omax;
     if (emax > max) max = emax;
 
-    Py_BEGIN_ALLOW_THREADS
-    n = select(max, &ifdset, &ofdset, &efdset, tvp);
-    Py_END_ALLOW_THREADS
+    if (tvp)
+        deadline = _PyTime_GetMonotonicClock() + timeout;
+
+    do {
+        Py_BEGIN_ALLOW_THREADS
+        errno = 0;
+        n = select(max, &ifdset, &ofdset, &efdset, tvp);
+        Py_END_ALLOW_THREADS
+
+        if (errno != EINTR)
+            break;
+
+        /* select() was interrupted by a signal */
+        if (PyErr_CheckSignals())
+            goto finally;
+
+        if (tvp) {
+            timeout = deadline - _PyTime_GetMonotonicClock();
+            if (timeout < 0) {
+                n = 0;
+                break;
+            }
+            _PyTime_AsTimeval_noraise(timeout, &tv, _PyTime_ROUND_CEILING);
+        }
+    } while (1);
 
 #ifdef MS_WINDOWS
     if (n == SOCKET_ERROR) {

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


More information about the Python-checkins mailing list