mirror of https://github.com/python/cpython
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:
parent
8620be99da
commit
58f8adfda3
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue