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.
This commit is contained in:
Victor Stinner 2015-03-19 21:54:09 +01:00
parent 0ed56a0b42
commit 79d68f929d
5 changed files with 101 additions and 66 deletions

View File

@ -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 requested by an arbitrary amount because of the scheduling of other activity
in the system. 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]) .. function:: strftime(format[, t])

View File

@ -252,8 +252,26 @@ class SocketEINTRTest(EINTRBaseTest):
lambda path: os.close(os.open(path, os.O_WRONLY))) 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(): def test_main():
support.run_unittest(OSEINTRTest, SocketEINTRTest) support.run_unittest(
OSEINTRTest,
SocketEINTRTest,
TimeEINTRTest)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -419,17 +419,20 @@ class WakeupSignalTests(unittest.TestCase):
TIMEOUT_HALF = 5 TIMEOUT_HALF = 5
signal.alarm(1) signal.alarm(1)
before_time = time.time()
# We attempt to get a signal during the sleep, # We attempt to get a signal during the sleep,
# before select is called # before select is called
time.sleep(TIMEOUT_FULL) try:
mid_time = time.time() select.select([], [], [], TIMEOUT_FULL)
dt = mid_time - before_time except InterruptedError:
if dt >= TIMEOUT_HALF: pass
raise Exception("%s >= %s" % (dt, TIMEOUT_HALF)) else:
raise Exception("select() was not interrupted")
before_time = time.monotonic()
select.select([read], [], [], TIMEOUT_FULL) select.select([read], [], [], TIMEOUT_FULL)
after_time = time.time() after_time = time.monotonic()
dt = after_time - mid_time dt = after_time - before_time
if dt >= TIMEOUT_HALF: if dt >= TIMEOUT_HALF:
raise Exception("%s >= %s" % (dt, TIMEOUT_HALF)) raise Exception("%s >= %s" % (dt, TIMEOUT_HALF))
""", signal.SIGALRM) """, signal.SIGALRM)
@ -443,7 +446,7 @@ class WakeupSignalTests(unittest.TestCase):
TIMEOUT_HALF = 5 TIMEOUT_HALF = 5
signal.alarm(1) signal.alarm(1)
before_time = time.time() before_time = time.monotonic()
# We attempt to get a signal during the select call # We attempt to get a signal during the select call
try: try:
select.select([read], [], [], TIMEOUT_FULL) select.select([read], [], [], TIMEOUT_FULL)
@ -451,7 +454,7 @@ class WakeupSignalTests(unittest.TestCase):
pass pass
else: else:
raise Exception("OSError not raised") raise Exception("OSError not raised")
after_time = time.time() after_time = time.monotonic()
dt = after_time - before_time dt = after_time - before_time
if dt >= TIMEOUT_HALF: if dt >= TIMEOUT_HALF:
raise Exception("%s >= %s" % (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.signal(signal.SIGVTALRM, self.sig_vtalrm)
signal.setitimer(self.itimer, 0.3, 0.2) signal.setitimer(self.itimer, 0.3, 0.2)
start_time = time.time() start_time = time.monotonic()
while time.time() - start_time < 60.0: while time.monotonic() - start_time < 60.0:
# use up some virtual time by doing real work # use up some virtual time by doing real work
_ = pow(12345, 67890, 10000019) _ = pow(12345, 67890, 10000019)
if signal.getitimer(self.itimer) == (0.0, 0.0): 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.signal(signal.SIGPROF, self.sig_prof)
signal.setitimer(self.itimer, 0.2, 0.2) signal.setitimer(self.itimer, 0.2, 0.2)
start_time = time.time() start_time = time.monotonic()
while time.time() - start_time < 60.0: while time.monotonic() - start_time < 60.0:
# do some work # do some work
_ = pow(12345, 67890, 10000019) _ = pow(12345, 67890, 10000019)
if signal.getitimer(self.itimer) == (0.0, 0.0): if signal.getitimer(self.itimer) == (0.0, 0.0):

View File

@ -18,6 +18,10 @@ Core and Builtins
Library 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 - Issue #23136: _strptime now uniformly handles all days in week 0, including
Dec 30 of previous year. Based on patch by Jim Carroll. Dec 30 of previous year. Based on patch by Jim Carroll.

View File

@ -1386,74 +1386,79 @@ floattime(_Py_clock_info_t *info)
static int static int
floatsleep(double secs) floatsleep(double secs)
{ {
/* XXX Should test for MS_WINDOWS first! */ _PyTime_timeval deadline, monotonic;
#if defined(HAVE_SELECT) && !defined(__EMX__) #ifndef MS_WINDOWS
struct timeval t; struct timeval timeout;
double frac; double frac;
int err; int err = 0;
#else
frac = fmod(secs, 1.0); double millisecs;
secs = floor(secs); unsigned long ul_millis;
t.tv_sec = (long)secs; DWORD rc;
t.tv_usec = (long)(frac*1000000.0); HANDLE hInterruptEvent;
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 #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); PyErr_SetFromErrno(PyExc_OSError);
return -1; return -1;
} }
} #else
#elif defined(__WATCOMC__) && !defined(__QNX__) millisecs = secs * 1000.0;
/* 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;
if (millisecs > (double)ULONG_MAX) { if (millisecs > (double)ULONG_MAX) {
PyErr_SetString(PyExc_OverflowError, PyErr_SetString(PyExc_OverflowError,
"sleep length is too large"); "sleep length is too large");
return -1; return -1;
} }
Py_BEGIN_ALLOW_THREADS
/* Allow sleep(0) to maintain win32 semantics, and as decreed /* Allow sleep(0) to maintain win32 semantics, and as decreed
* by Guido, only the main thread can be interrupted. * by Guido, only the main thread can be interrupted.
*/ */
ul_millis = (unsigned long)millisecs; ul_millis = (unsigned long)millisecs;
if (ul_millis == 0 || !_PyOS_IsMainThread()) if (ul_millis == 0 || !_PyOS_IsMainThread()) {
Sleep(ul_millis); Py_BEGIN_ALLOW_THREADS
else { Sleep(0);
DWORD rc; Py_END_ALLOW_THREADS
HANDLE hInterruptEvent = _PyOS_SigintEvent(); break;
ResetEvent(hInterruptEvent);
rc = WaitForSingleObjectEx(hInterruptEvent, ul_millis, FALSE);
if (rc == WAIT_OBJECT_0) {
Py_BLOCK_THREADS
errno = EINTR;
PyErr_SetFromErrno(PyExc_OSError);
return -1;
}
} }
hInterruptEvent = _PyOS_SigintEvent();
ResetEvent(hInterruptEvent);
Py_BEGIN_ALLOW_THREADS
rc = WaitForSingleObjectEx(hInterruptEvent, ul_millis, FALSE);
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
}
#else if (rc != WAIT_OBJECT_0)
/* XXX Can't interrupt this sleep */ break;
Py_BEGIN_ALLOW_THREADS
sleep((int)secs);
Py_END_ALLOW_THREADS
#endif #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; return 0;
} }