[Python-checkins] cpython: Issue #23715: signal.sigwaitinfo() and signal.sigtimedwait() are now retried

victor.stinner python-checkins at python.org
Fri Mar 20 13:01:58 CET 2015


https://hg.python.org/cpython/rev/33a6e2587aad
changeset:   95092:33a6e2587aad
user:        Victor Stinner <victor.stinner at gmail.com>
date:        Fri Mar 20 12:54:28 2015 +0100
summary:
  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.

files:
  Doc/library/signal.rst             |  10 ++
  Lib/test/eintrdata/eintr_tester.py |  38 +++++++++-
  Lib/test/test_signal.py            |  29 -------
  Misc/NEWS                          |   5 +
  Modules/signalmodule.c             |  73 +++++++++++------
  5 files changed, 99 insertions(+), 56 deletions(-)


diff --git a/Doc/library/signal.rst b/Doc/library/signal.rst
--- a/Doc/library/signal.rst
+++ b/Doc/library/signal.rst
@@ -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 --git a/Lib/test/eintrdata/eintr_tester.py b/Lib/test/eintrdata/eintr_tester.py
--- a/Lib/test/eintrdata/eintr_tester.py
+++ b/Lib/test/eintrdata/eintr_tester.py
@@ -264,11 +264,47 @@
         self.assertGreaterEqual(dt, self.sleep_time)
 
 
+ at 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 --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py
--- a/Lib/test/test_signal.py
+++ b/Lib/test/test_signal.py
@@ -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 --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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 --git a/Modules/signalmodule.c b/Modules/signalmodule.c
--- a/Modules/signalmodule.c
+++ b/Modules/signalmodule.c
@@ -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);
 }

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list