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 <egyszeregy@freemail.hu>
This commit is contained in:
Victor Stinner 2021-09-22 16:09:30 +02:00 committed by GitHub
parent 8620be99da
commit 58f8adfda3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 161 additions and 56 deletions

View File

@ -351,22 +351,35 @@ Functions
Suspend execution of the calling thread for the given number of seconds.
The argument may be a floating point number to indicate a more precise sleep
time. The actual suspension time may be less than that requested because any
caught signal will terminate the :func:`sleep` following execution of that
signal's catching routine. Also, the suspension time may be longer than
requested by an arbitrary amount because of the scheduling of other activity
in the system.
time.
If the sleep is interrupted by a signal and no exception is raised by the
signal handler, the sleep is restarted with a recomputed timeout.
The suspension time may be longer than requested by an arbitrary amount,
because of the scheduling of other activity in the system.
On Windows, if *secs* is zero, the thread relinquishes the remainder of its
time slice to any other thread that is ready to run. If there are no other
threads ready to run, the function returns immediately, and the thread
continues execution.
Implementation:
* On Unix, ``clock_nanosleep()`` is used if available (resolution: 1 ns),
or ``select()`` is used otherwise (resolution: 1 us).
* On Windows, a waitable timer is used (resolution: 100 ns). If *secs* is
zero, ``Sleep(0)`` is used.
.. versionchanged:: 3.11
On Unix, the ``clock_nanosleep()`` function is now used if available.
On Windows, a waitable timer is now used.
.. 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).
.. versionchanged:: 3.11
In Unix operating systems, the ``clock_nanosleep()`` function is now
used, if available: it allows to sleep for an interval specified with
nanosecond precision.
.. index::
single: % (percent); datetime format

View File

@ -234,9 +234,14 @@ sqlite3
time
----
* In Unix operating systems, :func:`time.sleep` now uses the
``clock_nanosleep()`` function, if available, which allows to sleep for an
interval specified with nanosecond precision.
* On Unix, :func:`time.sleep` now uses the ``clock_nanosleep()`` function, if
available, which has a resolution of 1 ns (10^-6 sec), rather than using
``select()`` which has a resolution of 1 us (10^-9 sec).
(Contributed by Livius and Victor Stinner in :issue:`21302`.)
* On Windows, :func:`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).
(Contributed by Livius and Victor Stinner in :issue:`21302`.)
unicodedata

View File

@ -114,6 +114,12 @@ PyAPI_FUNC(_PyTime_t) _PyTime_AsMicroseconds(_PyTime_t t,
/* Convert timestamp to a number of nanoseconds (10^-9 seconds). */
PyAPI_FUNC(_PyTime_t) _PyTime_AsNanoseconds(_PyTime_t t);
#ifdef MS_WINDOWS
// Convert timestamp to a number of 100 nanoseconds (10^-7 seconds).
PyAPI_FUNC(_PyTime_t) _PyTime_As100Nanoseconds(_PyTime_t t,
_PyTime_round_t round);
#endif
/* Convert timestamp to a number of nanoseconds (10^-9 seconds) as a Python int
object. */
PyAPI_FUNC(PyObject *) _PyTime_AsNanosecondsObject(_PyTime_t t);

View File

@ -0,0 +1,3 @@
On Windows, :func:`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).
Patch by Livius and Victor Stinner.

View File

@ -367,8 +367,9 @@ time_sleep(PyObject *self, PyObject *obj)
"sleep length must be non-negative");
return NULL;
}
if (pysleep(secs) != 0)
if (pysleep(secs) != 0) {
return NULL;
}
Py_RETURN_NONE;
}
@ -2044,47 +2045,42 @@ PyInit_time(void)
return PyModuleDef_Init(&timemodule);
}
/* Implement pysleep() for various platforms.
When interrupted (or when another error occurs), return -1 and
set an exception; else return 0. */
// time.sleep() implementation.
// On error, raise an exception and return -1.
// On success, return 0.
static int
pysleep(_PyTime_t secs)
{
_PyTime_t deadline, monotonic;
assert(secs >= 0);
#ifndef MS_WINDOWS
#ifdef HAVE_CLOCK_NANOSLEEP
struct timespec timeout_abs;
#else
struct timeval timeout;
#endif
_PyTime_t deadline, monotonic;
int err = 0;
int ret = 0;
#else
_PyTime_t millisecs;
unsigned long ul_millis;
DWORD rc;
HANDLE hInterruptEvent;
#endif
if (get_monotonic(&monotonic) < 0) {
return -1;
}
deadline = monotonic + secs;
#if defined(HAVE_CLOCK_NANOSLEEP) && !defined(MS_WINDOWS)
#ifdef HAVE_CLOCK_NANOSLEEP
if (_PyTime_AsTimespec(deadline, &timeout_abs) < 0) {
return -1;
}
#endif
do {
#ifndef MS_WINDOWS
#ifndef HAVE_CLOCK_NANOSLEEP
if (_PyTime_AsTimeval(secs, &timeout, _PyTime_ROUND_CEILING) < 0) {
return -1;
}
#endif
int ret;
#ifdef HAVE_CLOCK_NANOSLEEP
Py_BEGIN_ALLOW_THREADS
ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &timeout_abs, NULL);
@ -2106,35 +2102,6 @@ pysleep(_PyTime_t secs)
PyErr_SetFromErrno(PyExc_OSError);
return -1;
}
#else
millisecs = _PyTime_AsMilliseconds(secs, _PyTime_ROUND_CEILING);
if (millisecs > (double)ULONG_MAX) {
PyErr_SetString(PyExc_OverflowError,
"sleep length is too large");
return -1;
}
/* 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()) {
Py_BEGIN_ALLOW_THREADS
Sleep(ul_millis);
Py_END_ALLOW_THREADS
break;
}
hInterruptEvent = _PyOS_SigintEvent();
ResetEvent(hInterruptEvent);
Py_BEGIN_ALLOW_THREADS
rc = WaitForSingleObjectEx(hInterruptEvent, ul_millis, FALSE);
Py_END_ALLOW_THREADS
if (rc != WAIT_OBJECT_0)
break;
#endif
/* sleep was interrupted by SIGINT */
if (PyErr_CheckSignals()) {
@ -2154,4 +2121,104 @@ pysleep(_PyTime_t secs)
} while (1);
return 0;
#else // MS_WINDOWS
_PyTime_t timeout = _PyTime_As100Nanoseconds(secs, _PyTime_ROUND_CEILING);
// Maintain Windows Sleep() semantics for time.sleep(0)
if (timeout == 0) {
Py_BEGIN_ALLOW_THREADS
// A value of zero causes the thread to relinquish the remainder of its
// time slice to any other thread that is ready to run. If there are no
// other threads ready to run, the function returns immediately, and
// the thread continues execution.
Sleep(0);
Py_END_ALLOW_THREADS
return 0;
}
LARGE_INTEGER relative_timeout;
// No need to check for integer overflow, both types are signed
assert(sizeof(relative_timeout) == sizeof(timeout));
// SetWaitableTimer(): a negative due time indicates relative time
relative_timeout.QuadPart = -timeout;
HANDLE timer = CreateWaitableTimerW(NULL, FALSE, NULL);
if (timer == NULL) {
PyErr_SetFromWindowsErr(0);
return -1;
}
if (!SetWaitableTimer(timer, &relative_timeout,
// period: the timer is signaled once
0,
// no completion routine
NULL, NULL,
// Don't restore a system in suspended power
// conservation mode when the timer is signaled.
FALSE))
{
PyErr_SetFromWindowsErr(0);
goto error;
}
// Only the main thread can be interrupted by SIGINT.
// Signal handlers are only executed in the main thread.
if (_PyOS_IsMainThread()) {
HANDLE sigint_event = _PyOS_SigintEvent();
while (1) {
// Check for pending SIGINT signal before resetting the event
if (PyErr_CheckSignals()) {
goto error;
}
ResetEvent(sigint_event);
HANDLE events[] = {timer, sigint_event};
DWORD rc;
Py_BEGIN_ALLOW_THREADS
rc = WaitForMultipleObjects(Py_ARRAY_LENGTH(events), events,
// bWaitAll
FALSE,
// No wait timeout
INFINITE);
Py_END_ALLOW_THREADS
if (rc == WAIT_FAILED) {
PyErr_SetFromWindowsErr(0);
goto error;
}
if (rc == WAIT_OBJECT_0) {
// Timer signaled: we are done
break;
}
assert(rc == (WAIT_OBJECT_0 + 1));
// The sleep was interrupted by SIGINT: restart sleeping
}
}
else {
DWORD rc;
Py_BEGIN_ALLOW_THREADS
rc = WaitForSingleObject(timer, INFINITE);
Py_END_ALLOW_THREADS
if (rc == WAIT_FAILED) {
PyErr_SetFromWindowsErr(0);
goto error;
}
assert(rc == WAIT_OBJECT_0);
// Timer signaled: we are done
}
CloseHandle(timer);
return 0;
error:
CloseHandle(timer);
return -1;
#endif
}

View File

@ -33,6 +33,7 @@
/* Conversion from nanoseconds */
#define NS_TO_MS (1000 * 1000)
#define NS_TO_US (1000)
#define NS_TO_100NS (100)
static void
@ -568,6 +569,16 @@ _PyTime_AsNanoseconds(_PyTime_t t)
}
#ifdef MS_WINDOWS
_PyTime_t
_PyTime_As100Nanoseconds(_PyTime_t t, _PyTime_round_t round)
{
_PyTime_t ns = pytime_as_nanoseconds(t);
return pytime_divide(ns, NS_TO_100NS, round);
}
#endif
_PyTime_t
_PyTime_AsMicroseconds(_PyTime_t t, _PyTime_round_t round)
{