changeset: 95092:33a6e2587aad user: Victor Stinner date: Fri Mar 20 12:54:28 2015 +0100 files: Doc/library/signal.rst Lib/test/eintrdata/eintr_tester.py Lib/test/test_signal.py Misc/NEWS Modules/signalmodule.c description: 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. diff -r 07fd54208434 -r 33a6e2587aad Doc/library/signal.rst --- a/Doc/library/signal.rst Fri Mar 20 11:58:18 2015 +0100 +++ b/Doc/library/signal.rst Fri Mar 20 12:54:28 2015 +0100 @@ -408,6 +408,11 @@ .. 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) @@ -422,6 +427,11 @@ .. 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: diff -r 07fd54208434 -r 33a6e2587aad Lib/test/eintrdata/eintr_tester.py --- a/Lib/test/eintrdata/eintr_tester.py Fri Mar 20 11:58:18 2015 +0100 +++ b/Lib/test/eintrdata/eintr_tester.py Fri Mar 20 12:54:28 2015 +0100 @@ -264,11 +264,47 @@ 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(): support.run_unittest( OSEINTRTest, SocketEINTRTest, - TimeEINTRTest) + TimeEINTRTest, + SignalEINTRTest) if __name__ == "__main__": diff -r 07fd54208434 -r 33a6e2587aad Lib/test/test_signal.py --- a/Lib/test/test_signal.py Fri Mar 20 11:58:18 2015 +0100 +++ b/Lib/test/test_signal.py Fri Mar 20 12:54:28 2015 +0100 @@ -936,35 +936,6 @@ signum = signal.SIGALRM 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'), 'need signal.sigwait()') @unittest.skipUnless(hasattr(signal, 'pthread_sigmask'), diff -r 07fd54208434 -r 33a6e2587aad Misc/NEWS --- a/Misc/NEWS Fri Mar 20 11:58:18 2015 +0100 +++ b/Misc/NEWS Fri Mar 20 12:54:28 2015 +0100 @@ -21,6 +21,11 @@ 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 codecs, that accepted only read-only bytes-like object now accept writable bytes-like object too. diff -r 07fd54208434 -r 33a6e2587aad Modules/signalmodule.c --- a/Modules/signalmodule.c Fri Mar 20 11:58:18 2015 +0100 +++ b/Modules/signalmodule.c Fri Mar 20 12:54:28 2015 +0100 @@ -934,6 +934,7 @@ sigset_t set; siginfo_t si; int err; + int async_err = 0; if (!PyArg_ParseTuple(args, "O:sigwaitinfo", &signals)) return NULL; @@ -941,11 +942,14 @@ if (iterable_to_sigset(signals, &set)) return NULL; - Py_BEGIN_ALLOW_THREADS - err = sigwaitinfo(&set, &si); - Py_END_ALLOW_THREADS + do { + Py_BEGIN_ALLOW_THREADS + err = sigwaitinfo(&set, &si); + Py_END_ALLOW_THREADS + } while (err == -1 + && errno == EINTR && !(async_err = PyErr_CheckSignals())); if (err == -1) - return PyErr_SetFromErrno(PyExc_OSError); + return (!async_err) ? PyErr_SetFromErrno(PyExc_OSError) : NULL; return fill_siginfo(&si); } @@ -962,25 +966,19 @@ static PyObject * signal_sigtimedwait(PyObject *self, PyObject *args) { - PyObject *signals, *timeout; - struct timespec buf; + PyObject *signals; + double timeout, frac; + struct timespec ts; sigset_t set; siginfo_t si; - time_t tv_sec; - long tv_nsec; - int err; + int res; + _PyTime_timeval deadline, monotonic; - if (!PyArg_ParseTuple(args, "OO:sigtimedwait", + if (!PyArg_ParseTuple(args, "Od:sigtimedwait", &signals, &timeout)) return NULL; - if (_PyTime_ObjectToTimespec(timeout, &tv_sec, &tv_nsec, - _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) { + if (timeout < 0) { PyErr_SetString(PyExc_ValueError, "timeout must be non-negative"); return NULL; } @@ -988,15 +986,38 @@ if (iterable_to_sigset(signals, &set)) return NULL; - Py_BEGIN_ALLOW_THREADS - err = sigtimedwait(&set, &si, &buf); - Py_END_ALLOW_THREADS - if (err == -1) { - if (errno == EAGAIN) - Py_RETURN_NONE; - else - return PyErr_SetFromErrno(PyExc_OSError); - } + _PyTime_monotonic(&deadline); + _PyTime_AddDouble(&deadline, timeout, _PyTime_ROUND_UP); + + do { + frac = fmod(timeout, 1.0); + timeout = floor(timeout); + ts.tv_sec = (long)timeout; + 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); }