summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/time.rst5
-rw-r--r--Lib/test/eintrdata/eintr_tester.py20
-rw-r--r--Lib/test/test_signal.py31
-rw-r--r--Misc/NEWS4
-rw-r--r--Modules/timemodule.c107
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;
}