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.
This commit is contained in:
parent
3f5d48bead
commit
f70e1ca0fc
|
@ -41,7 +41,10 @@ defined by the module. The specific list of defined symbols is available as
|
||||||
|
|
||||||
.. data:: EINTR
|
.. data:: EINTR
|
||||||
|
|
||||||
Interrupted system call
|
Interrupted system call.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
This error is mapped to the exception :exc:`InterruptedError`.
|
||||||
|
|
||||||
|
|
||||||
.. data:: EIO
|
.. data:: EIO
|
||||||
|
|
|
@ -536,7 +536,12 @@ depending on the system error code.
|
||||||
.. exception:: InterruptedError
|
.. exception:: InterruptedError
|
||||||
|
|
||||||
Raised when a system call is interrupted by an incoming signal.
|
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
|
.. exception:: IsADirectoryError
|
||||||
|
|
||||||
|
|
|
@ -145,6 +145,13 @@ The module defines the following:
|
||||||
library, and does not handle file descriptors that don't originate from
|
library, and does not handle file descriptors that don't originate from
|
||||||
WinSock.
|
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
|
.. attribute:: PIPE_BUF
|
||||||
|
|
||||||
The minimum number of bytes which can be written without blocking to a pipe
|
The minimum number of bytes which can be written without blocking to a pipe
|
||||||
|
|
|
@ -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: Retry system calls failing with EINTR
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
|
|
||||||
:pep:`475` adds support for automatic retry of system calls failing with EINTR:
|
:pep:`475` adds support for automatic retry of system calls failing with
|
||||||
this means that user code doesn't have to deal with EINTR or InterruptedError
|
:py:data:`~errno.EINTR`: this means that user code doesn't have to deal with
|
||||||
manually, and should make it more robust against asynchronous signal reception.
|
EINTR or :exc:`InterruptedError` manually, and should make it more robust
|
||||||
|
against asynchronous signal reception.
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
|
@ -614,12 +615,13 @@ that may require changes to your code.
|
||||||
Changes in the Python API
|
Changes in the Python API
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
* :pep:`475`: the following functions are now retried when interrupted instead
|
* :pep:`475`: Examples of functions which are now retried when interrupted
|
||||||
of raising :exc:`InterruptedError` if the signal handler does not raise
|
instead of raising :exc:`InterruptedError` if the signal handler does not
|
||||||
an exception:
|
raise an exception:
|
||||||
|
|
||||||
- :func:`os.open`, :func:`open`
|
- :func:`os.open`, :func:`open`
|
||||||
- :func:`os.read`, :func:`os.write`
|
- :func:`os.read`, :func:`os.write`
|
||||||
|
- :func:`select.select`
|
||||||
- :func:`time.sleep`
|
- :func:`time.sleep`
|
||||||
|
|
||||||
* Before Python 3.5, a :class:`datetime.time` object was considered to be false
|
* Before Python 3.5, a :class:`datetime.time` object was considered to be false
|
||||||
|
|
|
@ -141,10 +141,7 @@ def poll(timeout=0.0, map=None):
|
||||||
time.sleep(timeout)
|
time.sleep(timeout)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
r, w, e = select.select(r, w, e, timeout)
|
||||||
r, w, e = select.select(r, w, e, timeout)
|
|
||||||
except InterruptedError:
|
|
||||||
return
|
|
||||||
|
|
||||||
for fd in r:
|
for fd in r:
|
||||||
obj = map.get(fd)
|
obj = map.get(fd)
|
||||||
|
|
|
@ -310,10 +310,7 @@ class SelectSelector(_BaseSelectorImpl):
|
||||||
def select(self, timeout=None):
|
def select(self, timeout=None):
|
||||||
timeout = None if timeout is None else max(timeout, 0)
|
timeout = None if timeout is None else max(timeout, 0)
|
||||||
ready = []
|
ready = []
|
||||||
try:
|
r, w, _ = self._select(self._readers, self._writers, [], timeout)
|
||||||
r, w, _ = self._select(self._readers, self._writers, [], timeout)
|
|
||||||
except InterruptedError:
|
|
||||||
return ready
|
|
||||||
r = set(r)
|
r = set(r)
|
||||||
w = set(w)
|
w = set(w)
|
||||||
for fd in r | w:
|
for fd in r | w:
|
||||||
|
|
|
@ -10,6 +10,7 @@ sub-second periodicity (contrarily to signal()).
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
|
import select
|
||||||
import signal
|
import signal
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
|
@ -303,12 +304,25 @@ class SignalEINTRTest(EINTRBaseTest):
|
||||||
self.assertGreaterEqual(dt, self.sleep_time)
|
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():
|
def test_main():
|
||||||
support.run_unittest(
|
support.run_unittest(
|
||||||
OSEINTRTest,
|
OSEINTRTest,
|
||||||
SocketEINTRTest,
|
SocketEINTRTest,
|
||||||
TimeEINTRTest,
|
TimeEINTRTest,
|
||||||
SignalEINTRTest)
|
SignalEINTRTest,
|
||||||
|
SelectEINTRTest)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -13,6 +13,10 @@ Core and Builtins
|
||||||
Library
|
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
|
- Issue #23752: When built from an existing file descriptor, io.FileIO() now
|
||||||
only calls fstat() once. Before fstat() was called twice, which was not
|
only calls fstat() once. Before fstat() was called twice, which was not
|
||||||
necessary.
|
necessary.
|
||||||
|
|
|
@ -193,29 +193,31 @@ select_select(PyObject *self, PyObject *args)
|
||||||
#endif /* SELECT_USES_HEAP */
|
#endif /* SELECT_USES_HEAP */
|
||||||
PyObject *ifdlist, *ofdlist, *efdlist;
|
PyObject *ifdlist, *ofdlist, *efdlist;
|
||||||
PyObject *ret = NULL;
|
PyObject *ret = NULL;
|
||||||
PyObject *tout = Py_None;
|
PyObject *timeout_obj = Py_None;
|
||||||
fd_set ifdset, ofdset, efdset;
|
fd_set ifdset, ofdset, efdset;
|
||||||
struct timeval tv, *tvp;
|
struct timeval tv, *tvp;
|
||||||
int imax, omax, emax, max;
|
int imax, omax, emax, max;
|
||||||
int n;
|
int n;
|
||||||
|
_PyTime_t timeout, deadline = 0;
|
||||||
|
|
||||||
/* convert arguments */
|
/* convert arguments */
|
||||||
if (!PyArg_UnpackTuple(args, "select", 3, 4,
|
if (!PyArg_UnpackTuple(args, "select", 3, 4,
|
||||||
&ifdlist, &ofdlist, &efdlist, &tout))
|
&ifdlist, &ofdlist, &efdlist, &timeout_obj))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (tout == Py_None)
|
if (timeout_obj == Py_None)
|
||||||
tvp = (struct timeval *)0;
|
tvp = (struct timeval *)NULL;
|
||||||
else {
|
else {
|
||||||
_PyTime_t ts;
|
if (_PyTime_FromSecondsObject(&timeout, timeout_obj,
|
||||||
|
_PyTime_ROUND_CEILING) < 0) {
|
||||||
if (_PyTime_FromSecondsObject(&ts, tout, _PyTime_ROUND_CEILING) < 0) {
|
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
|
||||||
PyErr_SetString(PyExc_TypeError,
|
PyErr_SetString(PyExc_TypeError,
|
||||||
"timeout must be a float or None");
|
"timeout must be a float or None");
|
||||||
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_PyTime_AsTimeval(ts, &tv, _PyTime_ROUND_CEILING) == -1)
|
if (_PyTime_AsTimeval(timeout, &tv, _PyTime_ROUND_CEILING) == -1)
|
||||||
return NULL;
|
return NULL;
|
||||||
if (tv.tv_sec < 0) {
|
if (tv.tv_sec < 0) {
|
||||||
PyErr_SetString(PyExc_ValueError, "timeout must be non-negative");
|
PyErr_SetString(PyExc_ValueError, "timeout must be non-negative");
|
||||||
|
@ -224,7 +226,6 @@ select_select(PyObject *self, PyObject *args)
|
||||||
tvp = &tv;
|
tvp = &tv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef SELECT_USES_HEAP
|
#ifdef SELECT_USES_HEAP
|
||||||
/* Allocate memory for the lists */
|
/* Allocate memory for the lists */
|
||||||
rfd2obj = PyMem_NEW(pylist, FD_SETSIZE + 1);
|
rfd2obj = PyMem_NEW(pylist, FD_SETSIZE + 1);
|
||||||
|
@ -237,6 +238,7 @@ select_select(PyObject *self, PyObject *args)
|
||||||
return PyErr_NoMemory();
|
return PyErr_NoMemory();
|
||||||
}
|
}
|
||||||
#endif /* SELECT_USES_HEAP */
|
#endif /* SELECT_USES_HEAP */
|
||||||
|
|
||||||
/* Convert sequences to fd_sets, and get maximum fd number
|
/* Convert sequences to fd_sets, and get maximum fd number
|
||||||
* propagates the Python exception set in seq2set()
|
* propagates the Python exception set in seq2set()
|
||||||
*/
|
*/
|
||||||
|
@ -249,13 +251,36 @@ select_select(PyObject *self, PyObject *args)
|
||||||
goto finally;
|
goto finally;
|
||||||
if ((emax=seq2set(efdlist, &efdset, efd2obj)) < 0)
|
if ((emax=seq2set(efdlist, &efdset, efd2obj)) < 0)
|
||||||
goto finally;
|
goto finally;
|
||||||
|
|
||||||
max = imax;
|
max = imax;
|
||||||
if (omax > max) max = omax;
|
if (omax > max) max = omax;
|
||||||
if (emax > max) max = emax;
|
if (emax > max) max = emax;
|
||||||
|
|
||||||
Py_BEGIN_ALLOW_THREADS
|
if (tvp)
|
||||||
n = select(max, &ifdset, &ofdset, &efdset, tvp);
|
deadline = _PyTime_GetMonotonicClock() + timeout;
|
||||||
Py_END_ALLOW_THREADS
|
|
||||||
|
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
|
#ifdef MS_WINDOWS
|
||||||
if (n == SOCKET_ERROR) {
|
if (n == SOCKET_ERROR) {
|
||||||
|
|
Loading…
Reference in New Issue