mirror of https://github.com/python/cpython
Issue #23715: signal.sigwaitinfo() and signal.sigtimedwait() are now retried
when interrupted by a signal not in the *sigset* parameter, if the signal handler does not raise an exception. signal.sigtimedwait() recomputes the timeout with a monotonic clock when it is retried. Remove test_signal.test_sigwaitinfo_interrupted() because sigwaitinfo() doesn't raise InterruptedError anymore if it is interrupted by a signal not in its sigset parameter.
This commit is contained in:
parent
a3c0202eb5
commit
a453cd8d85
|
@ -408,6 +408,11 @@ The :mod:`signal` module defines the following functions:
|
||||||
|
|
||||||
.. versionadded:: 3.3
|
.. versionadded:: 3.3
|
||||||
|
|
||||||
|
.. versionchanged:: 3.5
|
||||||
|
The function is now retried if interrupted by a signal not in *sigset*
|
||||||
|
and the signal handler does not raise an exception (see :pep:`475` for
|
||||||
|
the rationale).
|
||||||
|
|
||||||
|
|
||||||
.. function:: sigtimedwait(sigset, timeout)
|
.. function:: sigtimedwait(sigset, timeout)
|
||||||
|
|
||||||
|
@ -422,6 +427,11 @@ The :mod:`signal` module defines the following functions:
|
||||||
|
|
||||||
.. versionadded:: 3.3
|
.. versionadded:: 3.3
|
||||||
|
|
||||||
|
.. versionchanged:: 3.5
|
||||||
|
The function is now retried with the recomputed timeout if interrupted by
|
||||||
|
a signal not in *sigset* and the signal handler does not raise an
|
||||||
|
exception (see :pep:`475` for the rationale).
|
||||||
|
|
||||||
|
|
||||||
.. _signal-example:
|
.. _signal-example:
|
||||||
|
|
||||||
|
|
|
@ -264,11 +264,47 @@ class TimeEINTRTest(EINTRBaseTest):
|
||||||
self.assertGreaterEqual(dt, self.sleep_time)
|
self.assertGreaterEqual(dt, self.sleep_time)
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipUnless(hasattr(signal, "setitimer"), "requires setitimer()")
|
||||||
|
class SignalEINTRTest(EINTRBaseTest):
|
||||||
|
""" EINTR tests for the signal module. """
|
||||||
|
|
||||||
|
def test_sigtimedwait(self):
|
||||||
|
t0 = time.monotonic()
|
||||||
|
signal.sigtimedwait([], self.sleep_time)
|
||||||
|
dt = time.monotonic() - t0
|
||||||
|
self.assertGreaterEqual(dt, self.sleep_time)
|
||||||
|
|
||||||
|
def test_sigwaitinfo(self):
|
||||||
|
signum = signal.SIGUSR1
|
||||||
|
pid = os.getpid()
|
||||||
|
|
||||||
|
old_handler = signal.signal(signum, lambda *args: None)
|
||||||
|
self.addCleanup(signal.signal, signum, old_handler)
|
||||||
|
|
||||||
|
t0 = time.monotonic()
|
||||||
|
child_pid = os.fork()
|
||||||
|
if child_pid == 0:
|
||||||
|
# child
|
||||||
|
try:
|
||||||
|
self._sleep()
|
||||||
|
os.kill(pid, signum)
|
||||||
|
finally:
|
||||||
|
os._exit(0)
|
||||||
|
else:
|
||||||
|
# parent
|
||||||
|
signal.sigwaitinfo([signum])
|
||||||
|
dt = time.monotonic() - t0
|
||||||
|
os.waitpid(child_pid, 0)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -936,35 +936,6 @@ class PendingSignalsTests(unittest.TestCase):
|
||||||
signum = signal.SIGALRM
|
signum = signal.SIGALRM
|
||||||
self.assertRaises(ValueError, signal.sigtimedwait, [signum], -1.0)
|
self.assertRaises(ValueError, signal.sigtimedwait, [signum], -1.0)
|
||||||
|
|
||||||
@unittest.skipUnless(hasattr(signal, 'sigwaitinfo'),
|
|
||||||
'need signal.sigwaitinfo()')
|
|
||||||
# Issue #18238: sigwaitinfo() can be interrupted on Linux (raises
|
|
||||||
# InterruptedError), but not on AIX
|
|
||||||
@unittest.skipIf(sys.platform.startswith("aix"),
|
|
||||||
'signal.sigwaitinfo() cannot be interrupted on AIX')
|
|
||||||
def test_sigwaitinfo_interrupted(self):
|
|
||||||
self.wait_helper(signal.SIGUSR1, '''
|
|
||||||
def test(signum):
|
|
||||||
import errno
|
|
||||||
|
|
||||||
hndl_called = True
|
|
||||||
def alarm_handler(signum, frame):
|
|
||||||
hndl_called = False
|
|
||||||
|
|
||||||
signal.signal(signal.SIGALRM, alarm_handler)
|
|
||||||
signal.alarm(1)
|
|
||||||
try:
|
|
||||||
signal.sigwaitinfo([signal.SIGUSR1])
|
|
||||||
except OSError as e:
|
|
||||||
if e.errno == errno.EINTR:
|
|
||||||
if not hndl_called:
|
|
||||||
raise Exception("SIGALRM handler not called")
|
|
||||||
else:
|
|
||||||
raise Exception("Expected EINTR to be raised by sigwaitinfo")
|
|
||||||
else:
|
|
||||||
raise Exception("Expected EINTR to be raised by sigwaitinfo")
|
|
||||||
''')
|
|
||||||
|
|
||||||
@unittest.skipUnless(hasattr(signal, 'sigwait'),
|
@unittest.skipUnless(hasattr(signal, 'sigwait'),
|
||||||
'need signal.sigwait()')
|
'need signal.sigwait()')
|
||||||
@unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
|
@unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
|
||||||
|
|
|
@ -21,6 +21,11 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #23715: :func:`signal.sigwaitinfo` and :func:`signal.sigtimedwait` are
|
||||||
|
now retried when interrupted by a signal not in the *sigset* parameter, if
|
||||||
|
the signal handler does not raise an exception. signal.sigtimedwait()
|
||||||
|
recomputes the timeout with a monotonic clock when it is retried.
|
||||||
|
|
||||||
- Issue #23001: Few functions in modules mmap, ossaudiodev, socket, ssl, and
|
- Issue #23001: Few functions in modules mmap, ossaudiodev, socket, ssl, and
|
||||||
codecs, that accepted only read-only bytes-like object now accept writable
|
codecs, that accepted only read-only bytes-like object now accept writable
|
||||||
bytes-like object too.
|
bytes-like object too.
|
||||||
|
|
|
@ -934,6 +934,7 @@ signal_sigwaitinfo(PyObject *self, PyObject *args)
|
||||||
sigset_t set;
|
sigset_t set;
|
||||||
siginfo_t si;
|
siginfo_t si;
|
||||||
int err;
|
int err;
|
||||||
|
int async_err = 0;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O:sigwaitinfo", &signals))
|
if (!PyArg_ParseTuple(args, "O:sigwaitinfo", &signals))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -941,11 +942,14 @@ signal_sigwaitinfo(PyObject *self, PyObject *args)
|
||||||
if (iterable_to_sigset(signals, &set))
|
if (iterable_to_sigset(signals, &set))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
Py_BEGIN_ALLOW_THREADS
|
do {
|
||||||
err = sigwaitinfo(&set, &si);
|
Py_BEGIN_ALLOW_THREADS
|
||||||
Py_END_ALLOW_THREADS
|
err = sigwaitinfo(&set, &si);
|
||||||
|
Py_END_ALLOW_THREADS
|
||||||
|
} while (err == -1
|
||||||
|
&& errno == EINTR && !(async_err = PyErr_CheckSignals()));
|
||||||
if (err == -1)
|
if (err == -1)
|
||||||
return PyErr_SetFromErrno(PyExc_OSError);
|
return (!async_err) ? PyErr_SetFromErrno(PyExc_OSError) : NULL;
|
||||||
|
|
||||||
return fill_siginfo(&si);
|
return fill_siginfo(&si);
|
||||||
}
|
}
|
||||||
|
@ -962,25 +966,19 @@ Returns a struct_siginfo containing information about the signal.");
|
||||||
static PyObject *
|
static PyObject *
|
||||||
signal_sigtimedwait(PyObject *self, PyObject *args)
|
signal_sigtimedwait(PyObject *self, PyObject *args)
|
||||||
{
|
{
|
||||||
PyObject *signals, *timeout;
|
PyObject *signals;
|
||||||
struct timespec buf;
|
double timeout, frac;
|
||||||
|
struct timespec ts;
|
||||||
sigset_t set;
|
sigset_t set;
|
||||||
siginfo_t si;
|
siginfo_t si;
|
||||||
time_t tv_sec;
|
int res;
|
||||||
long tv_nsec;
|
_PyTime_timeval deadline, monotonic;
|
||||||
int err;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "OO:sigtimedwait",
|
if (!PyArg_ParseTuple(args, "Od:sigtimedwait",
|
||||||
&signals, &timeout))
|
&signals, &timeout))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (_PyTime_ObjectToTimespec(timeout, &tv_sec, &tv_nsec,
|
if (timeout < 0) {
|
||||||
_PyTime_ROUND_DOWN) == -1)
|
|
||||||
return NULL;
|
|
||||||
buf.tv_sec = tv_sec;
|
|
||||||
buf.tv_nsec = tv_nsec;
|
|
||||||
|
|
||||||
if (buf.tv_sec < 0 || buf.tv_nsec < 0) {
|
|
||||||
PyErr_SetString(PyExc_ValueError, "timeout must be non-negative");
|
PyErr_SetString(PyExc_ValueError, "timeout must be non-negative");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -988,15 +986,38 @@ signal_sigtimedwait(PyObject *self, PyObject *args)
|
||||||
if (iterable_to_sigset(signals, &set))
|
if (iterable_to_sigset(signals, &set))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
Py_BEGIN_ALLOW_THREADS
|
_PyTime_monotonic(&deadline);
|
||||||
err = sigtimedwait(&set, &si, &buf);
|
_PyTime_AddDouble(&deadline, timeout, _PyTime_ROUND_UP);
|
||||||
Py_END_ALLOW_THREADS
|
|
||||||
if (err == -1) {
|
do {
|
||||||
if (errno == EAGAIN)
|
frac = fmod(timeout, 1.0);
|
||||||
Py_RETURN_NONE;
|
timeout = floor(timeout);
|
||||||
else
|
ts.tv_sec = (long)timeout;
|
||||||
return PyErr_SetFromErrno(PyExc_OSError);
|
ts.tv_nsec = (long)(frac*1e9);
|
||||||
}
|
|
||||||
|
Py_BEGIN_ALLOW_THREADS
|
||||||
|
res = sigtimedwait(&set, &si, &ts);
|
||||||
|
Py_END_ALLOW_THREADS
|
||||||
|
|
||||||
|
if (res != -1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (errno != EINTR) {
|
||||||
|
if (errno == EAGAIN)
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
else
|
||||||
|
return PyErr_SetFromErrno(PyExc_OSError);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sigtimedwait() was interrupted by a signal (EINTR) */
|
||||||
|
if (PyErr_CheckSignals())
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
_PyTime_monotonic(&monotonic);
|
||||||
|
timeout = _PyTime_INTERVAL(monotonic, deadline);
|
||||||
|
if (timeout <= 0.0)
|
||||||
|
break;
|
||||||
|
} while (1);
|
||||||
|
|
||||||
return fill_siginfo(&si);
|
return fill_siginfo(&si);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue