summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/errno.rst5
-rw-r--r--Doc/library/exceptions.rst7
-rw-r--r--Doc/library/select.rst7
-rw-r--r--Doc/whatsnew/3.5.rst14
-rw-r--r--Lib/asyncore.py5
-rw-r--r--Lib/selectors.py5
-rw-r--r--Lib/test/eintrdata/eintr_tester.py16
-rw-r--r--Misc/NEWS4
-rw-r--r--Modules/selectmodule.c53
9 files changed, 85 insertions, 31 deletions
diff --git a/Doc/library/errno.rst b/Doc/library/errno.rst
index d2163b6..22a5cbc 100644
--- a/Doc/library/errno.rst
+++ b/Doc/library/errno.rst
@@ -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
diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst
index 271a5c8..bddd0ed 100644
--- a/Doc/library/exceptions.rst
+++ b/Doc/library/exceptions.rst
@@ -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
diff --git a/Doc/library/select.rst b/Doc/library/select.rst
index 5334af8..7fe09cb 100644
--- a/Doc/library/select.rst
+++ b/Doc/library/select.rst
@@ -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
diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst
index d33dfe8..3f70a94 100644
--- a/Doc/whatsnew/3.5.rst
+++ b/Doc/whatsnew/3.5.rst
@@ -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
diff --git a/Lib/asyncore.py b/Lib/asyncore.py
index 68efd45..5578dda 100644
--- a/Lib/asyncore.py
+++ b/Lib/asyncore.py
@@ -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)
diff --git a/Lib/selectors.py b/Lib/selectors.py
index 6d569c3..4f2a377 100644
--- a/Lib/selectors.py
+++ b/Lib/selectors.py
@@ -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:
diff --git a/Lib/test/eintrdata/eintr_tester.py b/Lib/test/eintrdata/eintr_tester.py
index ba056fb..82cef83 100644
--- a/Lib/test/eintrdata/eintr_tester.py
+++ b/Lib/test/eintrdata/eintr_tester.py
@@ -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__":
diff --git a/Misc/NEWS b/Misc/NEWS
index 36415588..b24ffb5 100644
--- 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.
diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c
index a852344..a6d6a83 100644
--- a/Modules/selectmodule.c
+++ b/Modules/selectmodule.c
@@ -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) {