diff --git a/Doc/library/time.rst b/Doc/library/time.rst index 9f23a6fc7d5341..564679144990da 100644 --- a/Doc/library/time.rst +++ b/Doc/library/time.rst @@ -388,6 +388,22 @@ Functions by a signal, except if the signal handler raises an exception (see :pep:`475` for the rationale). +.. function:: sleep_until(secs) + + Like :func:`sleep`, but sleep until the specified time of the system clock (as + returned by :func:`time`). This can be used, for example, to schedule events + at a specific timestamp obtained from + :meth:`datetime.timestamp `. + + See the notes in :func:`sleep` on the behavior when interrupted and on accuracy. + Additional potential sources of inaccuracies include: + + * Because this function uses the system clock as a reference, this means the + reference clock is adjustable and may jump backwards. + * On Unix, if ``clock_nanosleep()`` is not available, the absolute timeout + is emulated using ``nanosleep()`` or ``select()``. + + .. versionadded:: 3.12 .. index:: single: % (percent); datetime format diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 02cc3f43a66a67..0675707bf6855c 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -159,6 +159,18 @@ def test_sleep(self): self.assertRaises(ValueError, time.sleep, -1) time.sleep(1.2) + def test_sleep_until(self): + start = time.time() + deadline = start + 2 + time.sleep_until(deadline) + stop = time.time() + delta = stop - deadline + # cargo-cult these 50ms from test_monotonic (bpo-20101) + self.assertGreater(delta, -0.050) + # allow sleep_until to take up to 1s longer than planned + # (e.g. in case the system is under heavy load during testing) + self.assertLess(delta, 1.000) + def test_epoch(self): # bpo-43869: Make sure that Python use the same Epoch on all platforms: # January 1, 1970, 00:00:00 (UTC). diff --git a/Misc/ACKS b/Misc/ACKS index 1e94d33a665e4c..39bd4c2232d0a6 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -403,6 +403,7 @@ Lisandro Dalcin Darren Dale Andrew Dalke Lars Damerow +Hauke Dämpfling Evan Dandrea Eric Daniel Scott David Daniels diff --git a/Misc/NEWS.d/next/Library/2023-02-04-15-35-47.gh-issue-101558.ocOYkj.rst b/Misc/NEWS.d/next/Library/2023-02-04-15-35-47.gh-issue-101558.ocOYkj.rst new file mode 100644 index 00000000000000..20ecbebc6a36d2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-02-04-15-35-47.gh-issue-101558.ocOYkj.rst @@ -0,0 +1,2 @@ +Added the :func:`time.sleep_until` function, which allows sleeping until the +specified absolute time. diff --git a/Modules/timemodule.c b/Modules/timemodule.c index c50e689bb6986c..fce828d4272d58 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -115,7 +115,7 @@ _PyTime_Init(void) /* Forward declarations */ -static int pysleep(_PyTime_t timeout); +static int pysleep(_PyTime_t timeout, int absolute); typedef struct { @@ -422,7 +422,7 @@ time_sleep(PyObject *self, PyObject *timeout_obj) "sleep length must be non-negative"); return NULL; } - if (pysleep(timeout) != 0) { + if (pysleep(timeout, 0) != 0) { return NULL; } Py_RETURN_NONE; @@ -434,6 +434,29 @@ PyDoc_STRVAR(sleep_doc, Delay execution for a given number of seconds. The argument may be\n\ a floating point number for subsecond precision."); +static PyObject * +time_sleep_until(PyObject *self, PyObject *deadline_obj) +{ + _PyTime_t deadline; + if (_PyTime_FromSecondsObject(&deadline, deadline_obj, _PyTime_ROUND_TIMEOUT)) { + return NULL; + } + if (deadline < 0) { + PyErr_SetString(PyExc_ValueError, + "sleep_until deadline must be non-negative"); + return NULL; + } + if (pysleep(deadline, 1) != 0) { + return NULL; + } + Py_RETURN_NONE; +} + +PyDoc_STRVAR(sleep_until_doc, +"sleep_until(seconds)\n\ +\n\ +Delay execution until the specified system clock time."); + static PyStructSequence_Field struct_time_type_fields[] = { {"tm_year", "year, for example, 1993"}, {"tm_mon", "month of year, range [1, 12]"}, @@ -1868,6 +1891,7 @@ static PyMethodDef time_methods[] = { {"pthread_getcpuclockid", time_pthread_getcpuclockid, METH_VARARGS, pthread_getcpuclockid_doc}, #endif {"sleep", time_sleep, METH_O, sleep_doc}, + {"sleep_until", time_sleep_until, METH_O, sleep_until_doc}, {"gmtime", time_gmtime, METH_VARARGS, gmtime_doc}, {"localtime", time_localtime, METH_VARARGS, localtime_doc}, {"asctime", time_asctime, METH_VARARGS, asctime_doc}, @@ -2132,8 +2156,9 @@ PyInit_time(void) // time.sleep() implementation. // On error, raise an exception and return -1. // On success, return 0. +// If absolute==0, timeout is relative; otherwise timeout is absolute. static int -pysleep(_PyTime_t timeout) +pysleep(_PyTime_t timeout, int absolute) { assert(timeout >= 0); @@ -2145,13 +2170,27 @@ pysleep(_PyTime_t timeout) #else struct timeval timeout_tv; #endif - _PyTime_t deadline, monotonic; + _PyTime_t deadline, reference; int err = 0; - if (get_monotonic(&monotonic) < 0) { - return -1; + if (absolute) { + deadline = timeout; +#ifndef HAVE_CLOCK_NANOSLEEP + if (get_system_time(&reference) < 0) { + return -1; + } + timeout = deadline - reference; + if (timeout < 0) { + return 0; + } +#endif + } + else { + if (get_monotonic(&reference) < 0) { + return -1; + } + deadline = reference + timeout; } - deadline = monotonic + timeout; #ifdef HAVE_CLOCK_NANOSLEEP if (_PyTime_AsTimespec(deadline, &timeout_abs) < 0) { return -1; @@ -2174,7 +2213,8 @@ pysleep(_PyTime_t timeout) int ret; Py_BEGIN_ALLOW_THREADS #ifdef HAVE_CLOCK_NANOSLEEP - ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &timeout_abs, NULL); + ret = clock_nanosleep(absolute ? CLOCK_REALTIME : CLOCK_MONOTONIC, + TIMER_ABSTIME, &timeout_abs, NULL); err = ret; #elif defined(HAVE_NANOSLEEP) ret = nanosleep(&timeout_ts, NULL); @@ -2201,10 +2241,17 @@ pysleep(_PyTime_t timeout) } #ifndef HAVE_CLOCK_NANOSLEEP - if (get_monotonic(&monotonic) < 0) { - return -1; + if (absolute) { + if (get_system_time(&reference) < 0) { + return -1; + } } - timeout = deadline - monotonic; + else { + if (get_monotonic(&reference) < 0) { + return -1; + } + } + timeout = deadline - reference; if (timeout < 0) { break; } @@ -2229,11 +2276,18 @@ pysleep(_PyTime_t timeout) return 0; } - LARGE_INTEGER relative_timeout; + LARGE_INTEGER due_time; // No need to check for integer overflow, both types are signed - assert(sizeof(relative_timeout) == sizeof(timeout_100ns)); - // SetWaitableTimer(): a negative due time indicates relative time - relative_timeout.QuadPart = -timeout_100ns; + assert(sizeof(due_time) == sizeof(timeout_100ns)); + if (absolute) { + // Adjust from Unix time (1970-01-01) to Windows time (1601-01-01) + // (the inverse of what is done in py_get_system_clock) + due_time.QuadPart = timeout_100ns + 116444736000000000; + } + else { + // SetWaitableTimer(): a negative due time indicates relative time + due_time.QuadPart = -timeout_100ns; + } HANDLE timer = CreateWaitableTimerExW(NULL, NULL, timer_flags, TIMER_ALL_ACCESS); @@ -2242,7 +2296,7 @@ pysleep(_PyTime_t timeout) return -1; } - if (!SetWaitableTimerEx(timer, &relative_timeout, + if (!SetWaitableTimerEx(timer, &due_time, 0, // no period; the timer is signaled once NULL, NULL, // no completion routine NULL, // no wake context; do not resume from suspend pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy