Skip to content

Commit 58f8adf

Browse files
vstinnerLivius90
andauthored
bpo-21302: time.sleep() uses waitable timer on Windows (GH-28483)
On Windows, time.sleep() now uses a waitable timer which has a resolution of 100 ns (10^-7 sec). Previously, it had a solution of 1 ms (10^-3 sec). * On Windows, time.sleep() now calls PyErr_CheckSignals() before resetting the SIGINT event. * Add _PyTime_As100Nanoseconds() function. * Complete and update time.sleep() documentation. Co-authored-by: Livius <[email protected]>
1 parent 8620be9 commit 58f8adf

File tree

6 files changed

+161
-56
lines changed

6 files changed

+161
-56
lines changed

Doc/library/time.rst

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -351,22 +351,35 @@ Functions
351351

352352
Suspend execution of the calling thread for the given number of seconds.
353353
The argument may be a floating point number to indicate a more precise sleep
354-
time. The actual suspension time may be less than that requested because any
355-
caught signal will terminate the :func:`sleep` following execution of that
356-
signal's catching routine. Also, the suspension time may be longer than
357-
requested by an arbitrary amount because of the scheduling of other activity
358-
in the system.
354+
time.
355+
356+
If the sleep is interrupted by a signal and no exception is raised by the
357+
signal handler, the sleep is restarted with a recomputed timeout.
358+
359+
The suspension time may be longer than requested by an arbitrary amount,
360+
because of the scheduling of other activity in the system.
361+
362+
On Windows, if *secs* is zero, the thread relinquishes the remainder of its
363+
time slice to any other thread that is ready to run. If there are no other
364+
threads ready to run, the function returns immediately, and the thread
365+
continues execution.
366+
367+
Implementation:
368+
369+
* On Unix, ``clock_nanosleep()`` is used if available (resolution: 1 ns),
370+
or ``select()`` is used otherwise (resolution: 1 us).
371+
* On Windows, a waitable timer is used (resolution: 100 ns). If *secs* is
372+
zero, ``Sleep(0)`` is used.
373+
374+
.. versionchanged:: 3.11
375+
On Unix, the ``clock_nanosleep()`` function is now used if available.
376+
On Windows, a waitable timer is now used.
359377

360378
.. versionchanged:: 3.5
361379
The function now sleeps at least *secs* even if the sleep is interrupted
362380
by a signal, except if the signal handler raises an exception (see
363381
:pep:`475` for the rationale).
364382

365-
.. versionchanged:: 3.11
366-
In Unix operating systems, the ``clock_nanosleep()`` function is now
367-
used, if available: it allows to sleep for an interval specified with
368-
nanosecond precision.
369-
370383

371384
.. index::
372385
single: % (percent); datetime format

Doc/whatsnew/3.11.rst

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,9 +234,14 @@ sqlite3
234234
time
235235
----
236236

237-
* In Unix operating systems, :func:`time.sleep` now uses the
238-
``clock_nanosleep()`` function, if available, which allows to sleep for an
239-
interval specified with nanosecond precision.
237+
* On Unix, :func:`time.sleep` now uses the ``clock_nanosleep()`` function, if
238+
available, which has a resolution of 1 ns (10^-6 sec), rather than using
239+
``select()`` which has a resolution of 1 us (10^-9 sec).
240+
(Contributed by Livius and Victor Stinner in :issue:`21302`.)
241+
242+
* On Windows, :func:`time.sleep` now uses a waitable timer which has a
243+
resolution of 100 ns (10^-7 sec). Previously, it had a solution of 1 ms
244+
(10^-3 sec).
240245
(Contributed by Livius and Victor Stinner in :issue:`21302`.)
241246

242247
unicodedata

Include/cpython/pytime.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,12 @@ PyAPI_FUNC(_PyTime_t) _PyTime_AsMicroseconds(_PyTime_t t,
114114
/* Convert timestamp to a number of nanoseconds (10^-9 seconds). */
115115
PyAPI_FUNC(_PyTime_t) _PyTime_AsNanoseconds(_PyTime_t t);
116116

117+
#ifdef MS_WINDOWS
118+
// Convert timestamp to a number of 100 nanoseconds (10^-7 seconds).
119+
PyAPI_FUNC(_PyTime_t) _PyTime_As100Nanoseconds(_PyTime_t t,
120+
_PyTime_round_t round);
121+
#endif
122+
117123
/* Convert timestamp to a number of nanoseconds (10^-9 seconds) as a Python int
118124
object. */
119125
PyAPI_FUNC(PyObject *) _PyTime_AsNanosecondsObject(_PyTime_t t);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
On Windows, :func:`time.sleep` now uses a waitable timer which has a resolution
2+
of 100 ns (10^-7 sec). Previously, it had a solution of 1 ms (10^-3 sec).
3+
Patch by Livius and Victor Stinner.

Modules/timemodule.c

Lines changed: 110 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -367,8 +367,9 @@ time_sleep(PyObject *self, PyObject *obj)
367367
"sleep length must be non-negative");
368368
return NULL;
369369
}
370-
if (pysleep(secs) != 0)
370+
if (pysleep(secs) != 0) {
371371
return NULL;
372+
}
372373
Py_RETURN_NONE;
373374
}
374375

@@ -2044,47 +2045,42 @@ PyInit_time(void)
20442045
return PyModuleDef_Init(&timemodule);
20452046
}
20462047

2047-
/* Implement pysleep() for various platforms.
2048-
When interrupted (or when another error occurs), return -1 and
2049-
set an exception; else return 0. */
20502048

2049+
// time.sleep() implementation.
2050+
// On error, raise an exception and return -1.
2051+
// On success, return 0.
20512052
static int
20522053
pysleep(_PyTime_t secs)
20532054
{
2054-
_PyTime_t deadline, monotonic;
2055+
assert(secs >= 0);
2056+
20552057
#ifndef MS_WINDOWS
20562058
#ifdef HAVE_CLOCK_NANOSLEEP
20572059
struct timespec timeout_abs;
20582060
#else
20592061
struct timeval timeout;
20602062
#endif
2063+
_PyTime_t deadline, monotonic;
20612064
int err = 0;
2062-
int ret = 0;
2063-
#else
2064-
_PyTime_t millisecs;
2065-
unsigned long ul_millis;
2066-
DWORD rc;
2067-
HANDLE hInterruptEvent;
2068-
#endif
20692065

20702066
if (get_monotonic(&monotonic) < 0) {
20712067
return -1;
20722068
}
20732069
deadline = monotonic + secs;
2074-
#if defined(HAVE_CLOCK_NANOSLEEP) && !defined(MS_WINDOWS)
2070+
#ifdef HAVE_CLOCK_NANOSLEEP
20752071
if (_PyTime_AsTimespec(deadline, &timeout_abs) < 0) {
20762072
return -1;
20772073
}
20782074
#endif
20792075

20802076
do {
2081-
#ifndef MS_WINDOWS
20822077
#ifndef HAVE_CLOCK_NANOSLEEP
20832078
if (_PyTime_AsTimeval(secs, &timeout, _PyTime_ROUND_CEILING) < 0) {
20842079
return -1;
20852080
}
20862081
#endif
20872082

2083+
int ret;
20882084
#ifdef HAVE_CLOCK_NANOSLEEP
20892085
Py_BEGIN_ALLOW_THREADS
20902086
ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &timeout_abs, NULL);
@@ -2106,35 +2102,6 @@ pysleep(_PyTime_t secs)
21062102
PyErr_SetFromErrno(PyExc_OSError);
21072103
return -1;
21082104
}
2109-
#else
2110-
millisecs = _PyTime_AsMilliseconds(secs, _PyTime_ROUND_CEILING);
2111-
if (millisecs > (double)ULONG_MAX) {
2112-
PyErr_SetString(PyExc_OverflowError,
2113-
"sleep length is too large");
2114-
return -1;
2115-
}
2116-
2117-
/* Allow sleep(0) to maintain win32 semantics, and as decreed
2118-
* by Guido, only the main thread can be interrupted.
2119-
*/
2120-
ul_millis = (unsigned long)millisecs;
2121-
if (ul_millis == 0 || !_PyOS_IsMainThread()) {
2122-
Py_BEGIN_ALLOW_THREADS
2123-
Sleep(ul_millis);
2124-
Py_END_ALLOW_THREADS
2125-
break;
2126-
}
2127-
2128-
hInterruptEvent = _PyOS_SigintEvent();
2129-
ResetEvent(hInterruptEvent);
2130-
2131-
Py_BEGIN_ALLOW_THREADS
2132-
rc = WaitForSingleObjectEx(hInterruptEvent, ul_millis, FALSE);
2133-
Py_END_ALLOW_THREADS
2134-
2135-
if (rc != WAIT_OBJECT_0)
2136-
break;
2137-
#endif
21382105

21392106
/* sleep was interrupted by SIGINT */
21402107
if (PyErr_CheckSignals()) {
@@ -2154,4 +2121,104 @@ pysleep(_PyTime_t secs)
21542121
} while (1);
21552122

21562123
return 0;
2124+
#else // MS_WINDOWS
2125+
_PyTime_t timeout = _PyTime_As100Nanoseconds(secs, _PyTime_ROUND_CEILING);
2126+
2127+
// Maintain Windows Sleep() semantics for time.sleep(0)
2128+
if (timeout == 0) {
2129+
Py_BEGIN_ALLOW_THREADS
2130+
// A value of zero causes the thread to relinquish the remainder of its
2131+
// time slice to any other thread that is ready to run. If there are no
2132+
// other threads ready to run, the function returns immediately, and
2133+
// the thread continues execution.
2134+
Sleep(0);
2135+
Py_END_ALLOW_THREADS
2136+
return 0;
2137+
}
2138+
2139+
LARGE_INTEGER relative_timeout;
2140+
// No need to check for integer overflow, both types are signed
2141+
assert(sizeof(relative_timeout) == sizeof(timeout));
2142+
// SetWaitableTimer(): a negative due time indicates relative time
2143+
relative_timeout.QuadPart = -timeout;
2144+
2145+
HANDLE timer = CreateWaitableTimerW(NULL, FALSE, NULL);
2146+
if (timer == NULL) {
2147+
PyErr_SetFromWindowsErr(0);
2148+
return -1;
2149+
}
2150+
2151+
if (!SetWaitableTimer(timer, &relative_timeout,
2152+
// period: the timer is signaled once
2153+
0,
2154+
// no completion routine
2155+
NULL, NULL,
2156+
// Don't restore a system in suspended power
2157+
// conservation mode when the timer is signaled.
2158+
FALSE))
2159+
{
2160+
PyErr_SetFromWindowsErr(0);
2161+
goto error;
2162+
}
2163+
2164+
// Only the main thread can be interrupted by SIGINT.
2165+
// Signal handlers are only executed in the main thread.
2166+
if (_PyOS_IsMainThread()) {
2167+
HANDLE sigint_event = _PyOS_SigintEvent();
2168+
2169+
while (1) {
2170+
// Check for pending SIGINT signal before resetting the event
2171+
if (PyErr_CheckSignals()) {
2172+
goto error;
2173+
}
2174+
ResetEvent(sigint_event);
2175+
2176+
HANDLE events[] = {timer, sigint_event};
2177+
DWORD rc;
2178+
2179+
Py_BEGIN_ALLOW_THREADS
2180+
rc = WaitForMultipleObjects(Py_ARRAY_LENGTH(events), events,
2181+
// bWaitAll
2182+
FALSE,
2183+
// No wait timeout
2184+
INFINITE);
2185+
Py_END_ALLOW_THREADS
2186+
2187+
if (rc == WAIT_FAILED) {
2188+
PyErr_SetFromWindowsErr(0);
2189+
goto error;
2190+
}
2191+
2192+
if (rc == WAIT_OBJECT_0) {
2193+
// Timer signaled: we are done
2194+
break;
2195+
}
2196+
2197+
assert(rc == (WAIT_OBJECT_0 + 1));
2198+
// The sleep was interrupted by SIGINT: restart sleeping
2199+
}
2200+
}
2201+
else {
2202+
DWORD rc;
2203+
2204+
Py_BEGIN_ALLOW_THREADS
2205+
rc = WaitForSingleObject(timer, INFINITE);
2206+
Py_END_ALLOW_THREADS
2207+
2208+
if (rc == WAIT_FAILED) {
2209+
PyErr_SetFromWindowsErr(0);
2210+
goto error;
2211+
}
2212+
2213+
assert(rc == WAIT_OBJECT_0);
2214+
// Timer signaled: we are done
2215+
}
2216+
2217+
CloseHandle(timer);
2218+
return 0;
2219+
2220+
error:
2221+
CloseHandle(timer);
2222+
return -1;
2223+
#endif
21572224
}

Python/pytime.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
/* Conversion from nanoseconds */
3434
#define NS_TO_MS (1000 * 1000)
3535
#define NS_TO_US (1000)
36+
#define NS_TO_100NS (100)
3637

3738

3839
static void
@@ -568,6 +569,16 @@ _PyTime_AsNanoseconds(_PyTime_t t)
568569
}
569570

570571

572+
#ifdef MS_WINDOWS
573+
_PyTime_t
574+
_PyTime_As100Nanoseconds(_PyTime_t t, _PyTime_round_t round)
575+
{
576+
_PyTime_t ns = pytime_as_nanoseconds(t);
577+
return pytime_divide(ns, NS_TO_100NS, round);
578+
}
579+
#endif
580+
581+
571582
_PyTime_t
572583
_PyTime_AsMicroseconds(_PyTime_t t, _PyTime_round_t round)
573584
{

0 commit comments

Comments
 (0)