From 79d68f929d8def878766965f513b628023f809b5 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 19 Mar 2015 21:54:09 +0100 Subject: 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. --- 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 index 6bfd521..3d335c8 100644 --- a/Doc/library/time.rst +++ b/Doc/library/time.rst @@ -350,6 +350,11 @@ The module defines the following functions and data items: 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 index 40dca84..400dd21 100644 --- a/Lib/test/eintrdata/eintr_tester.py +++ b/Lib/test/eintrdata/eintr_tester.py @@ -252,8 +252,26 @@ class SocketEINTRTest(EINTRBaseTest): lambda path: os.close(os.open(path, os.O_WRONLY))) +@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 index 774fc80..4e7cbe2 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -419,17 +419,20 @@ class WakeupSignalTests(unittest.TestCase): 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 @@ class WakeupSignalTests(unittest.TestCase): 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 @@ class WakeupSignalTests(unittest.TestCase): 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 @@ class ItimerTest(unittest.TestCase): 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 @@ class ItimerTest(unittest.TestCase): 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 index cea41ee..4dd5c44 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -18,6 +18,10 @@ Core and Builtins 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 index 7f5f314..179c33f 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -1386,74 +1386,79 @@ floattime(_Py_clock_info_t *info) 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; - - 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 + int err = 0; +#else + double millisecs; + unsigned long ul_millis; + DWORD rc; + HANDLE hInterruptEvent; #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; } -- cgit v0.12