]> granicus.if.org Git - python/commitdiff
Issue #23485: select.select() is now retried automatically with the recomputed
authorVictor Stinner <victor.stinner@gmail.com>
Mon, 30 Mar 2015 19:16:11 +0000 (21:16 +0200)
committerVictor Stinner <victor.stinner@gmail.com>
Mon, 30 Mar 2015 19:16:11 +0000 (21:16 +0200)
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.

Doc/library/errno.rst
Doc/library/exceptions.rst
Doc/library/select.rst
Doc/whatsnew/3.5.rst
Lib/asyncore.py
Lib/selectors.py
Lib/test/eintrdata/eintr_tester.py
Misc/NEWS
Modules/selectmodule.c

index d2163b62582c9711e023069d740d03dcd426333e..22a5cbc4507e123d2a3f842870ff9c4ebe2ad998 100644 (file)
@@ -41,7 +41,10 @@ defined by the module.  The specific list of defined symbols is available as
 
 .. data:: EINTR
 
-   Interrupted system call
+   Interrupted system call.
+
+   .. seealso::
+      This error is mapped to the exception :exc:`InterruptedError`.
 
 
 .. data:: EIO
index 271a5c82194ca41b60b39dff75568ec005c18021..bddd0edc6c825a57950ec8792a699038fd639bec 100644 (file)
@@ -536,7 +536,12 @@ depending on the system error code.
 .. 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
 
index 5334af8ea4ae9bcac27e56c58552551a51124cc7..7fe09cbe2978883d6f874b54bfb49fca7c6a2abf 100644 (file)
@@ -145,6 +145,13 @@ The module defines the following:
       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
index d33dfe8e70264717dd8d465e82974bd08808abeb..3f70a944660af97fbdaad48b4193b6837761ce24 100644 (file)
@@ -173,9 +173,10 @@ PEP and implementation written by Ben Hoyt with the help of Victor Stinner.
 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 @@ that may require changes to your code.
 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
index 68efd45f858a989364ffb64684c65da7ed8be825..5578ddab59777c7a0370ffe27d20a58196d79cdc 100644 (file)
@@ -141,10 +141,7 @@ def poll(timeout=0.0, map=None):
             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)
index 6d569c30adff871c2346fb05d6ef0359432f7c54..4f2a3771208706204e6d6ed4d7572c9f9600134f 100644 (file)
@@ -310,10 +310,7 @@ class SelectSelector(_BaseSelectorImpl):
     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:
index ba056fbb93fabd54a5d2bb182e9d026694b19f2f..82cef83d2c00c81a8fc73e4d09696777c9190815 100644 (file)
@@ -10,6 +10,7 @@ sub-second periodicity (contrarily to signal()).
 
 import io
 import os
+import select
 import signal
 import socket
 import time
@@ -303,12 +304,25 @@ class SignalEINTRTest(EINTRBaseTest):
         self.assertGreaterEqual(dt, self.sleep_time)
 
 
+@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__":
index 36415588b75cb21886d92e6a8b3d4c55c9d7cef0..b24ffb5120557ab74a22053e0df68fb9cc11755c 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -13,6 +13,10 @@ Core and Builtins
 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.
index a8523440a9a4f22293505f32f99682e6baf198d7..a6d6a837504c895c6f9a2246ae875e1c03afadb4 100644 (file)
@@ -193,29 +193,31 @@ select_select(PyObject *self, PyObject *args)
 #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 @@ select_select(PyObject *self, PyObject *args)
         tvp = &tv;
     }
 
-
 #ifdef SELECT_USES_HEAP
     /* Allocate memory for the lists */
     rfd2obj = PyMem_NEW(pylist, FD_SETSIZE + 1);
@@ -237,6 +238,7 @@ select_select(PyObject *self, PyObject *args)
         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 @@ select_select(PyObject *self, PyObject *args)
         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) {