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
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])

View File

@ -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__":

View File

@ -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):

View File

@ -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.

View File

@ -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;
}