summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/time.rst33
-rw-r--r--Doc/whatsnew/3.11.rst11
-rw-r--r--Include/cpython/pytime.h6
-rw-r--r--Misc/NEWS.d/next/Library/2021-09-20-22-46-40.bpo-21302.h56430.rst3
-rw-r--r--Modules/timemodule.c153
-rw-r--r--Python/pytime.c11
6 files changed, 161 insertions, 56 deletions
diff --git a/Doc/library/time.rst b/Doc/library/time.rst
index 34cb28f..d91862c 100644
--- a/Doc/library/time.rst
+++ b/Doc/library/time.rst
@@ -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
diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst
index acc00d8..12e46c3 100644
--- a/Doc/whatsnew/3.11.rst
+++ b/Doc/whatsnew/3.11.rst
@@ -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
diff --git a/Include/cpython/pytime.h b/Include/cpython/pytime.h
index b045388..8c29585 100644
--- a/Include/cpython/pytime.h
+++ b/Include/cpython/pytime.h
@@ -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);
diff --git a/Misc/NEWS.d/next/Library/2021-09-20-22-46-40.bpo-21302.h56430.rst b/Misc/NEWS.d/next/Library/2021-09-20-22-46-40.bpo-21302.h56430.rst
new file mode 100644
index 0000000..22011b7
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-09-20-22-46-40.bpo-21302.h56430.rst
@@ -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.
diff --git a/Modules/timemodule.c b/Modules/timemodule.c
index 52c6115..53ec86e 100644
--- a/Modules/timemodule.c
+++ b/Modules/timemodule.c
@@ -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
}
diff --git a/Python/pytime.c b/Python/pytime.c
index 8035a5f..7f9f301 100644
--- a/Python/pytime.c
+++ b/Python/pytime.c
@@ -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)
{