[Python-checkins] cpython: Issue #23646: If time.sleep() is interrupted by a signal, the sleep is now

victor.stinner python-checkins at python.org
Thu Mar 19 21:59:57 CET 2015


https://hg.python.org/cpython/rev/b1abd06465fc
changeset:   95068:b1abd06465fc
user:        Victor Stinner <victor.stinner at gmail.com>
date:        Thu Mar 19 21:54:09 2015 +0100
summary:
  Issue #23646: If time.sleep() is interrupted by a signal, the sleep is now
retried with the recomputed delay, except if the signal handler raises an
exception (PEP 475).

Modify also test_signal to use a monotonic clock instead of the system clock.

files:
  Doc/library/time.rst               |    5 +
  Lib/test/eintrdata/eintr_tester.py |   20 +++-
  Lib/test/test_signal.py            |   31 ++--
  Misc/NEWS                          |    4 +
  Modules/timemodule.c               |  107 ++++++++--------
  5 files changed, 101 insertions(+), 66 deletions(-)


diff --git a/Doc/library/time.rst b/Doc/library/time.rst
--- a/Doc/library/time.rst
+++ b/Doc/library/time.rst
@@ -350,6 +350,11 @@
    requested by an arbitrary amount because of the scheduling of other activity
    in the system.
 
+   .. versionchanged:: 3.5
+      The function now sleeps at least *secs* even if the sleep is interrupted
+      by a signal, except if the signal handler raises an exception (see
+      :pep:`475` for the rationale).
+
 
 .. function:: strftime(format[, t])
 
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
@@ -252,8 +252,26 @@
                         lambda path: os.close(os.open(path, os.O_WRONLY)))
 
 
+ at unittest.skipUnless(hasattr(signal, "setitimer"), "requires setitimer()")
+class TimeEINTRTest(EINTRBaseTest):
+    """ EINTR tests for the time module. """
+
+    def test_sleep(self):
+        t0 = time.monotonic()
+        # time.sleep() may retry when interrupted by a signal
+        time.sleep(2)
+        signal.alarm(0)
+        dt = time.monotonic() - t0
+        # Tolerate a difference 100 ms: on Windows, time.monotonic() has
+        # a resolution of 15.6 ms or greater
+        self.assertGreaterEqual(dt, 1.9)
+
+
 def test_main():
-    support.run_unittest(OSEINTRTest, SocketEINTRTest)
+    support.run_unittest(
+        OSEINTRTest,
+        SocketEINTRTest,
+        TimeEINTRTest)
 
 
 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
@@ -419,17 +419,20 @@
             TIMEOUT_HALF = 5
 
             signal.alarm(1)
-            before_time = time.time()
+
             # We attempt to get a signal during the sleep,
             # before select is called
-            time.sleep(TIMEOUT_FULL)
-            mid_time = time.time()
-            dt = mid_time - before_time
-            if dt >= TIMEOUT_HALF:
-                raise Exception("%s >= %s" % (dt, TIMEOUT_HALF))
+            try:
+                select.select([], [], [], TIMEOUT_FULL)
+            except InterruptedError:
+                pass
+            else:
+                raise Exception("select() was not interrupted")
+
+            before_time = time.monotonic()
             select.select([read], [], [], TIMEOUT_FULL)
-            after_time = time.time()
-            dt = after_time - mid_time
+            after_time = time.monotonic()
+            dt = after_time - before_time
             if dt >= TIMEOUT_HALF:
                 raise Exception("%s >= %s" % (dt, TIMEOUT_HALF))
         """, signal.SIGALRM)
@@ -443,7 +446,7 @@
             TIMEOUT_HALF = 5
 
             signal.alarm(1)
-            before_time = time.time()
+            before_time = time.monotonic()
             # We attempt to get a signal during the select call
             try:
                 select.select([read], [], [], TIMEOUT_FULL)
@@ -451,7 +454,7 @@
                 pass
             else:
                 raise Exception("OSError not raised")
-            after_time = time.time()
+            after_time = time.monotonic()
             dt = after_time - before_time
             if dt >= TIMEOUT_HALF:
                 raise Exception("%s >= %s" % (dt, TIMEOUT_HALF))
@@ -709,8 +712,8 @@
         signal.signal(signal.SIGVTALRM, self.sig_vtalrm)
         signal.setitimer(self.itimer, 0.3, 0.2)
 
-        start_time = time.time()
-        while time.time() - start_time < 60.0:
+        start_time = time.monotonic()
+        while time.monotonic() - start_time < 60.0:
             # use up some virtual time by doing real work
             _ = pow(12345, 67890, 10000019)
             if signal.getitimer(self.itimer) == (0.0, 0.0):
@@ -732,8 +735,8 @@
         signal.signal(signal.SIGPROF, self.sig_prof)
         signal.setitimer(self.itimer, 0.2, 0.2)
 
-        start_time = time.time()
-        while time.time() - start_time < 60.0:
+        start_time = time.monotonic()
+        while time.monotonic() - start_time < 60.0:
             # do some work
             _ = pow(12345, 67890, 10000019)
             if signal.getitimer(self.itimer) == (0.0, 0.0):
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -18,6 +18,10 @@
 Library
 -------
 
+- Issue #23646: If time.sleep() is interrupted by a signal, the sleep is now
+  retried with the recomputed delay, except if the signal handler raises an
+  exception (PEP 475).
+
 - Issue #23136: _strptime now uniformly handles all days in week 0, including
   Dec 30 of previous year.  Based on patch by Jim Carroll.
 
diff --git a/Modules/timemodule.c b/Modules/timemodule.c
--- a/Modules/timemodule.c
+++ b/Modules/timemodule.c
@@ -1386,74 +1386,79 @@
 static int
 floatsleep(double secs)
 {
-/* XXX Should test for MS_WINDOWS first! */
-#if defined(HAVE_SELECT) && !defined(__EMX__)
-    struct timeval t;
+    _PyTime_timeval deadline, monotonic;
+#ifndef MS_WINDOWS
+    struct timeval timeout;
     double frac;
-    int err;
+    int err = 0;
+#else
+    double millisecs;
+    unsigned long ul_millis;
+    DWORD rc;
+    HANDLE hInterruptEvent;
+#endif
 
-    frac = fmod(secs, 1.0);
-    secs = floor(secs);
-    t.tv_sec = (long)secs;
-    t.tv_usec = (long)(frac*1000000.0);
-    Py_BEGIN_ALLOW_THREADS
-    err = select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &t);
-    Py_END_ALLOW_THREADS
-    if (err != 0) {
-#ifdef EINTR
-        if (errno == EINTR) {
-            if (PyErr_CheckSignals())
-                return -1;
-        }
-        else
-#endif
-        {
+    _PyTime_monotonic(&deadline);
+    _PyTime_ADD_SECONDS(deadline, secs);
+
+    do {
+#ifndef MS_WINDOWS
+        frac = fmod(secs, 1.0);
+        secs = floor(secs);
+        timeout.tv_sec = (long)secs;
+        timeout.tv_usec = (long)(frac*1000000.0);
+
+        Py_BEGIN_ALLOW_THREADS
+        err = select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &timeout);
+        Py_END_ALLOW_THREADS
+
+        if (err == 0)
+            break;
+
+        if (errno != EINTR) {
             PyErr_SetFromErrno(PyExc_OSError);
             return -1;
         }
-    }
-#elif defined(__WATCOMC__) && !defined(__QNX__)
-    /* XXX Can't interrupt this sleep */
-    Py_BEGIN_ALLOW_THREADS
-    delay((int)(secs * 1000 + 0.5));  /* delay() uses milliseconds */
-    Py_END_ALLOW_THREADS
-#elif defined(MS_WINDOWS)
-    {
-        double millisecs = secs * 1000.0;
-        unsigned long ul_millis;
-
+#else
+        millisecs = secs * 1000.0;
         if (millisecs > (double)ULONG_MAX) {
             PyErr_SetString(PyExc_OverflowError,
                             "sleep length is too large");
             return -1;
         }
-        Py_BEGIN_ALLOW_THREADS
+
         /* Allow sleep(0) to maintain win32 semantics, and as decreed
          * by Guido, only the main thread can be interrupted.
          */
         ul_millis = (unsigned long)millisecs;
-        if (ul_millis == 0 || !_PyOS_IsMainThread())
-            Sleep(ul_millis);
-        else {
-            DWORD rc;
-            HANDLE hInterruptEvent = _PyOS_SigintEvent();
-            ResetEvent(hInterruptEvent);
-            rc = WaitForSingleObjectEx(hInterruptEvent, ul_millis, FALSE);
-            if (rc == WAIT_OBJECT_0) {
-                Py_BLOCK_THREADS
-                errno = EINTR;
-                PyErr_SetFromErrno(PyExc_OSError);
-                return -1;
-            }
+        if (ul_millis == 0 || !_PyOS_IsMainThread()) {
+            Py_BEGIN_ALLOW_THREADS
+            Sleep(0);
+            Py_END_ALLOW_THREADS
+            break;
         }
+
+        hInterruptEvent = _PyOS_SigintEvent();
+        ResetEvent(hInterruptEvent);
+
+        Py_BEGIN_ALLOW_THREADS
+        rc = WaitForSingleObjectEx(hInterruptEvent, ul_millis, FALSE);
         Py_END_ALLOW_THREADS
-    }
-#else
-    /* XXX Can't interrupt this sleep */
-    Py_BEGIN_ALLOW_THREADS
-    sleep((int)secs);
-    Py_END_ALLOW_THREADS
+
+        if (rc != WAIT_OBJECT_0)
+            break;
 #endif
 
+        /* sleep was interrupted by SIGINT */
+        if (PyErr_CheckSignals())
+            return -1;
+
+        _PyTime_monotonic(&monotonic);
+        secs = _PyTime_INTERVAL(monotonic, deadline);
+        if (secs <= 0.0)
+            break;
+        /* retry with the recomputed delay */
+    } while (1);
+
     return 0;
 }

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


More information about the Python-checkins mailing list