From 3754e9e02f5195243d83f327bb1c9ec1ec8f8780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:16:14 +0100 Subject: [PATCH 01/26] Ensure that `time.sleep(0)` does not accumulate delays. On non-Windows platforms, this reverts the usage of `clock_nanosleep` and `nanosleep` introduced by 85a4748 and 7834ff2 respectively, falling back to a `select(2)` alternative instead. --- Modules/timemodule.c | 49 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 340011fc08b551..2d9593398cab89 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -72,6 +72,9 @@ module time /* Forward declarations */ static int pysleep(PyTime_t timeout); +#ifndef MS_WINDOWS +static int pysleep_zero_posix(void); +#endif typedef struct { @@ -2213,6 +2216,11 @@ static int pysleep(PyTime_t timeout) { assert(timeout >= 0); +#ifndef MS_WINDOWS + if (timeout == 0) { + return pysleep_zero_posix(); + } +#endif #ifndef MS_WINDOWS #ifdef HAVE_CLOCK_NANOSLEEP @@ -2390,3 +2398,44 @@ pysleep(PyTime_t timeout) return -1; #endif } + + +#ifndef MS_WINDWOS +// time.sleep(0) optimized implementation. +// On error, raise an exception and return -1. +// On success, return 0. +static int +pysleep_zero_posix(void) +{ + static struct timeval zero = {0, 0}; + + PyTime_t deadline, monotonic; + if (PyTime_Monotonic(&monotonic) < 0) { + return -1; + } + deadline = monotonic; + do { + int ret, err; + Py_BEGIN_ALLOW_THREADS + ret = select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &zero); + err = errno; + Py_END_ALLOW_THREADS + if (ret == 0) { + break; + } + if (err != EINTR) { + errno = err; + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + /* sleep was interrupted by SIGINT */ + if (PyErr_CheckSignals()) { + return -1; + } + if (PyTime_Monotonic(&monotonic) < 0) { + return -1; + } + } while (monotonic == deadline); + return 0; +} +#endif From 3c0c4e65287571013a2393fe4c98ecbe658c8fc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:30:25 +0100 Subject: [PATCH 02/26] update globals --- Tools/c-analyzer/cpython/ignored.tsv | 1 + 1 file changed, 1 insertion(+) diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index c8c30a7985aa2e..57c561f2cffce3 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -313,6 +313,7 @@ Modules/pyexpat.c - error_info_of - Modules/pyexpat.c - handler_info - Modules/termios.c - termios_constants - Modules/timemodule.c init_timezone YEAR - +Modules/timemodule.c pysleep_zero_posix zero - Objects/bytearrayobject.c - _PyByteArray_empty_string - Objects/complexobject.c - c_1 - Objects/exceptions.c - static_exceptions - From c18f15f94d47969dad32c47f7814ca6ebab96ecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:09:30 +0100 Subject: [PATCH 03/26] blurb --- .../next/Library/2024-12-26-13-09-25.gh-issue-125997.Vsibep.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-12-26-13-09-25.gh-issue-125997.Vsibep.rst diff --git a/Misc/NEWS.d/next/Library/2024-12-26-13-09-25.gh-issue-125997.Vsibep.rst b/Misc/NEWS.d/next/Library/2024-12-26-13-09-25.gh-issue-125997.Vsibep.rst new file mode 100644 index 00000000000000..88a3adb50914b7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-12-26-13-09-25.gh-issue-125997.Vsibep.rst @@ -0,0 +1,2 @@ +Ensure that :func:`time.sleep(0) ` does not accumulate delays on +non-Windows platforms. Patch by Bénédikt Tran. From 8d270f2ae5efa3fedc389949304a87e73feb0704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:35:59 +0100 Subject: [PATCH 04/26] fix Windows compilation --- Modules/timemodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 2d9593398cab89..ffbdef9d3736fe 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -2400,7 +2400,7 @@ pysleep(PyTime_t timeout) } -#ifndef MS_WINDWOS +#ifndef MS_WINDOWS // time.sleep(0) optimized implementation. // On error, raise an exception and return -1. // On success, return 0. From 2996937efc492804110a0bc8ab57f3a9bf6e35f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 27 Dec 2024 12:04:02 +0100 Subject: [PATCH 05/26] use a fresh `timeval` variable for select(2) portability --- Modules/timemodule.c | 12 ++++++++++-- Tools/c-analyzer/cpython/ignored.tsv | 1 - 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Modules/timemodule.c b/Modules/timemodule.c index ffbdef9d3736fe..f6be807df262c3 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -2407,8 +2407,6 @@ pysleep(PyTime_t timeout) static int pysleep_zero_posix(void) { - static struct timeval zero = {0, 0}; - PyTime_t deadline, monotonic; if (PyTime_Monotonic(&monotonic) < 0) { return -1; @@ -2416,6 +2414,16 @@ pysleep_zero_posix(void) deadline = monotonic; do { int ret, err; + // POSIX-compliant select(2) allows the 'timeout' parameter to + // be modified but also mandates that the function should return + // immediately if *both* structure's fields are zero (which is + // the case here). + // + // However, since System V (but not BSD) variant typically sets + // the timeout before returning (but does not specify whether + // this is also the case for zero timeouts), we prefer supplying + // a fresh timeout everytime. + struct timeval zero = {0, 0}; Py_BEGIN_ALLOW_THREADS ret = select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &zero); err = errno; diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index 57c561f2cffce3..c8c30a7985aa2e 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -313,7 +313,6 @@ Modules/pyexpat.c - error_info_of - Modules/pyexpat.c - handler_info - Modules/termios.c - termios_constants - Modules/timemodule.c init_timezone YEAR - -Modules/timemodule.c pysleep_zero_posix zero - Objects/bytearrayobject.c - _PyByteArray_empty_string - Objects/complexobject.c - c_1 - Objects/exceptions.c - static_exceptions - From 80de853bf1d0c50d46272ecd06339e5e010c1f51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 27 Dec 2024 12:05:03 +0100 Subject: [PATCH 06/26] ensure that `errno=0` when calling `pysleep[zero_posix]` --- Modules/timemodule.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/timemodule.c b/Modules/timemodule.c index f6be807df262c3..3eb9f494665bb0 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -2217,6 +2217,7 @@ pysleep(PyTime_t timeout) { assert(timeout >= 0); #ifndef MS_WINDOWS + assert(errno == 0); if (timeout == 0) { return pysleep_zero_posix(); } @@ -2407,6 +2408,7 @@ pysleep(PyTime_t timeout) static int pysleep_zero_posix(void) { + assert(errno == 0); PyTime_t deadline, monotonic; if (PyTime_Monotonic(&monotonic) < 0) { return -1; From c7aa42826c4bd45ee156d29d06ef47b857d8e92c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 27 Dec 2024 12:16:50 +0100 Subject: [PATCH 07/26] removing `assert(errno == 0)` Due to how `OSError` are raised from `errno`, we do not clear `errno` afterwards. If we catch `OSError`, then we still have an errno set, and if we call `time.sleep()` just after, we may have `errno != 0` (but we know we handled it so it's fine). --- Modules/timemodule.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 3eb9f494665bb0..f6be807df262c3 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -2217,7 +2217,6 @@ pysleep(PyTime_t timeout) { assert(timeout >= 0); #ifndef MS_WINDOWS - assert(errno == 0); if (timeout == 0) { return pysleep_zero_posix(); } @@ -2408,7 +2407,6 @@ pysleep(PyTime_t timeout) static int pysleep_zero_posix(void) { - assert(errno == 0); PyTime_t deadline, monotonic; if (PyTime_Monotonic(&monotonic) < 0) { return -1; From f15436e4751ba47a4834940b4b76436d876b693b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 27 Dec 2024 13:53:40 +0100 Subject: [PATCH 08/26] add errors assertions --- Modules/timemodule.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/timemodule.c b/Modules/timemodule.c index f6be807df262c3..dedcc67f619170 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -2216,6 +2216,7 @@ static int pysleep(PyTime_t timeout) { assert(timeout >= 0); + assert(!PyErr_Occurred()); #ifndef MS_WINDOWS if (timeout == 0) { return pysleep_zero_posix(); @@ -2407,6 +2408,7 @@ pysleep(PyTime_t timeout) static int pysleep_zero_posix(void) { + assert(!PyErr_Occurred()); PyTime_t deadline, monotonic; if (PyTime_Monotonic(&monotonic) < 0) { return -1; From 68e2988621d11db847b30046c67536fd1069f17d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 29 Dec 2024 08:59:03 +0100 Subject: [PATCH 09/26] simplify code --- Modules/timemodule.c | 59 ++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 35 deletions(-) diff --git a/Modules/timemodule.c b/Modules/timemodule.c index dedcc67f619170..6770c881cf13ef 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -2409,43 +2409,32 @@ static int pysleep_zero_posix(void) { assert(!PyErr_Occurred()); - PyTime_t deadline, monotonic; - if (PyTime_Monotonic(&monotonic) < 0) { + + int ret; + // POSIX-compliant select(2) allows the 'timeout' parameter to + // be modified but also mandates that the function should return + // immediately if *both* structure's fields are zero (which is + // the case here). + // + // However, since System V (but not BSD) variant typically sets + // the timeout before returning (but does not specify whether + // this is also the case for zero timeouts), we prefer supplying + // a fresh timeout everytime. + struct timeval zero = {0, 0}; + Py_BEGIN_ALLOW_THREADS + ret = select(0, NULL, NULL, NULL, &zero); + Py_END_ALLOW_THREADS + if (ret == 0) { + return 0; + } + if (errno != EINTR) { + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + /* sleep was interrupted by SIGINT */ + if (PyErr_CheckSignals()) { return -1; } - deadline = monotonic; - do { - int ret, err; - // POSIX-compliant select(2) allows the 'timeout' parameter to - // be modified but also mandates that the function should return - // immediately if *both* structure's fields are zero (which is - // the case here). - // - // However, since System V (but not BSD) variant typically sets - // the timeout before returning (but does not specify whether - // this is also the case for zero timeouts), we prefer supplying - // a fresh timeout everytime. - struct timeval zero = {0, 0}; - Py_BEGIN_ALLOW_THREADS - ret = select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &zero); - err = errno; - Py_END_ALLOW_THREADS - if (ret == 0) { - break; - } - if (err != EINTR) { - errno = err; - PyErr_SetFromErrno(PyExc_OSError); - return -1; - } - /* sleep was interrupted by SIGINT */ - if (PyErr_CheckSignals()) { - return -1; - } - if (PyTime_Monotonic(&monotonic) < 0) { - return -1; - } - } while (monotonic == deadline); return 0; } #endif From afc61860b9a0ec1ecacf60b81680045f13c600a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 30 Dec 2024 13:11:55 +0100 Subject: [PATCH 10/26] add comments for `time.sleep(0)` --- Modules/timemodule.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 6770c881cf13ef..8e5517b61f269d 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -2405,6 +2405,32 @@ pysleep(PyTime_t timeout) // time.sleep(0) optimized implementation. // On error, raise an exception and return -1. // On success, return 0. +// +// Rationale +// --------- +// time.sleep(0) accumulates delays if we use nanosleep() or clock_nanosleep(). +// To avoid this pitfall, we may either use select(0, NULL, NULL, NULL, &zero) +// or sched_yield(). The former is implementation-sensitive while the latter +// would explicit relinquish the CPU but is more portable [1]. +// +// While select() is less portable due to various implementation details +// it is slightly faster [2]. In addition, implicitly calling the kernel's +// algorithm in time.sleep(0) may not be what non-Windows users expect [3]. +// +// Therefore, we opt for a solution based on select() instead of sched_yield(). +// +// [1] On Linux, calling sched_yield() cause the kernel's scheduling algorithm +// to run as well and could be inefficient in terms of CPU consumption if +// time.sleep(0) is successively called multiple times. +// +// [2] Experimentally, the CPU consumption of a sched_yield() solution is +// similar to that one based on select(), albeit slightly slower. +// +// [3] sched_yield() is not recommended when resources needed by other +// schedulable threads are still held by the caller (which may be +// the case) and using it with with nondeterministic scheduling policies +// such as SCHED_OTHER (which is the default) results in an unspecified +// behaviour. static int pysleep_zero_posix(void) { From f6a817fe31aea1822091afe2c45e15a27fcb602b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 30 Dec 2024 13:21:59 +0100 Subject: [PATCH 11/26] improve time.sleep() tests --- Lib/test/test_time.py | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 1c540bed33c71e..83c0aead5fae71 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -158,10 +158,46 @@ def test_conversions(self): self.assertEqual(int(time.mktime(time.localtime(self.t))), int(self.t)) - def test_sleep(self): + def test_sleep_exceptions(self): + self.assertRaises(TypeError, time.sleep, []) + self.assertRaises(TypeError, time.sleep, "a") + self.assertRaises(TypeError, time.sleep, complex(0, 0)) + self.assertRaises(ValueError, time.sleep, -2) self.assertRaises(ValueError, time.sleep, -1) - time.sleep(1.2) + self.assertRaises(ValueError, time.sleep, -0.1) + + def test_sleep(self): + for value in [-0.0, 0, 0.0, 1e-6, 1, 1.2]: + with self.subTest(value=value): + time.sleep(value) + + @unittest.skipIf(support.MS_WINDOWS, 'test only for non-Windows platforms') + def test_sleep_zero_posix(self): + # Test that time.sleep(0) does not accumulate delays. + + N1 = 1000 # small number of samples for time.sleep(eps) with eps > 0 + N2 = 100_000 # large number of samples for time.sleep(0) + + # Compute how long time.sleep() takes for the 'time' clock resolution. + # We expect the result to be around 50 us for a nanosecond resolution. + eps = time.get_clock_info('time').resolution + max_dt_ns = self.stat_for_test_sleep(N1, time.sleep, eps) + + # We expect a gap between time.sleep(0) and time.sleep(eps). + avg_dt_ns = self.stat_for_test_sleep(N2, time.sleep, 0) + self.assertLess(avg_dt_ns, max_dt_ns) + + @staticmethod + def stat_for_test_sleep(n_samples, func, *args, **kwargs): + """Compute the average (ns) time execution of func(*args, **kwargs).""" + samples = [] + for _ in range(int(n_samples)): + t0 = time.monotonic_ns() + func(*args, **kwargs) + t1 = time.monotonic_ns() + samples.append(t1 - t0) + return math.fsum(samples) / n_samples def test_epoch(self): # bpo-43869: Make sure that Python use the same Epoch on all platforms: From fb9eb4129aaef9c28b69ec7a48965dee5d1e4a10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 30 Dec 2024 13:45:36 +0100 Subject: [PATCH 12/26] update `os` scheduling policy docs --- Doc/library/os.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 69e6192038ab2b..6d9ada309555d3 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -5411,6 +5411,8 @@ information, consult your Unix manpages. The following scheduling policies are exposed if they are supported by the operating system. +.. _os-scheduling-policy: + .. data:: SCHED_OTHER The default scheduling policy. @@ -5516,6 +5518,8 @@ operating system. Voluntarily relinquish the CPU. + See also :manpage:`sched_yield(2)`. + .. function:: sched_setaffinity(pid, mask, /) From ecb96365160bc01938eb663a88ca6cfe97928716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 30 Dec 2024 13:24:53 +0100 Subject: [PATCH 13/26] update docs --- Doc/library/time.rst | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Doc/library/time.rst b/Doc/library/time.rst index 6265c2214eaa0d..7927e044cdfa3c 100644 --- a/Doc/library/time.rst +++ b/Doc/library/time.rst @@ -385,6 +385,8 @@ Functions The suspension time may be longer than requested by an arbitrary amount, because of the scheduling of other activity in the system. + .. rubric:: Windows implementation + 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 @@ -393,12 +395,20 @@ Functions `_ which provides resolution of 100 nanoseconds. If *secs* is zero, ``Sleep(0)`` is used. - Unix implementation: + .. rubric:: Unix implementation + + If *secs* is zero, ``select()`` is used. Otherwise: * Use ``clock_nanosleep()`` if available (resolution: 1 nanosecond); * Or use ``nanosleep()`` if available (resolution: 1 nanosecond); * Or use ``select()`` (resolution: 1 microsecond). + .. note:: + + To voluntarily relinquish the CPU, specify a read-time :ref:`scheduling + policy ` (see :manpage:`sched_yield(2)`) and use + :func:`os.sched_yield` instead. + .. audit-event:: time.sleep secs .. versionchanged:: 3.5 @@ -413,6 +423,10 @@ Functions .. versionchanged:: 3.13 Raises an auditing event. + .. versionchanged:: next + On Unix, ``time.sleep(0)`` always uses ``select()``, even if the + ``clock_nanosleep()`` or ``nanosleep()`` functions are available. + .. index:: single: % (percent); datetime format From 1ed98770775eadd4fe107e49cba350218acba9f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 30 Dec 2024 13:28:26 +0100 Subject: [PATCH 14/26] add What's New --- Doc/whatsnew/3.14.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 0dcee56b7d233f..f7628ae4896fcd 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -617,6 +617,16 @@ sys.monitoring Two new events are added: :monitoring-event:`BRANCH_LEFT` and :monitoring-event:`BRANCH_RIGHT`. The ``BRANCH`` event is deprecated. + +time +---- + +* Ensure that :func:`time.sleep(0) ` does not accumulate delays on + POSIX platforms. The implementation always uses :manpage:`select(2)` even if + the :manpage:`clock_nanosleep` or :manpage:`nanosleep` functions are present. + (Contributed by Bénédikt Tran in :gh:`125997`.) + + tkinter ------- From 23b474002a1201fb7c4f52deea46baaeba90acee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 2 Jan 2025 10:40:11 +0100 Subject: [PATCH 15/26] fix typo --- Modules/timemodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 8e5517b61f269d..20581e67163218 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -2419,7 +2419,7 @@ pysleep(PyTime_t timeout) // // Therefore, we opt for a solution based on select() instead of sched_yield(). // -// [1] On Linux, calling sched_yield() cause the kernel's scheduling algorithm +// [1] On Linux, calling sched_yield() causes the kernel's scheduling algorithm // to run as well and could be inefficient in terms of CPU consumption if // time.sleep(0) is successively called multiple times. // From 40155ffbd597a991b2eb6dbe909b9e9f99cffea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 3 Jan 2025 09:54:23 +0100 Subject: [PATCH 16/26] optimize `time.sleep(0)` path --- Modules/timemodule.c | 48 ++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 20581e67163218..b54c62f1a66794 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -73,7 +73,7 @@ module time /* Forward declarations */ static int pysleep(PyTime_t timeout); #ifndef MS_WINDOWS -static int pysleep_zero_posix(void); +static int pysleep_zero_posix(void); // see gh-125997 #endif @@ -2218,7 +2218,7 @@ pysleep(PyTime_t timeout) assert(timeout >= 0); assert(!PyErr_Occurred()); #ifndef MS_WINDOWS - if (timeout == 0) { + if (timeout == 0) { // gh-125997 return pysleep_zero_posix(); } #endif @@ -2408,35 +2408,25 @@ pysleep(PyTime_t timeout) // // Rationale // --------- -// time.sleep(0) accumulates delays if we use nanosleep() or clock_nanosleep(). -// To avoid this pitfall, we may either use select(0, NULL, NULL, NULL, &zero) -// or sched_yield(). The former is implementation-sensitive while the latter -// would explicit relinquish the CPU but is more portable [1]. -// -// While select() is less portable due to various implementation details -// it is slightly faster [2]. In addition, implicitly calling the kernel's -// algorithm in time.sleep(0) may not be what non-Windows users expect [3]. -// -// Therefore, we opt for a solution based on select() instead of sched_yield(). -// -// [1] On Linux, calling sched_yield() causes the kernel's scheduling algorithm -// to run as well and could be inefficient in terms of CPU consumption if -// time.sleep(0) is successively called multiple times. -// -// [2] Experimentally, the CPU consumption of a sched_yield() solution is -// similar to that one based on select(), albeit slightly slower. -// -// [3] sched_yield() is not recommended when resources needed by other -// schedulable threads are still held by the caller (which may be -// the case) and using it with with nondeterministic scheduling policies -// such as SCHED_OTHER (which is the default) results in an unspecified -// behaviour. +// time.sleep(0) accumulates delays in the generic implementation, but we can +// skip some calls to `PyTime_Monotonic()` and other checks when the timeout +// is zero. For details, see https://github.com/python/cpython/pull/128274. static int pysleep_zero_posix(void) { assert(!PyErr_Occurred()); - int ret; + int ret, err; + Py_BEGIN_ALLOW_THREADS +#ifdef HAVE_CLOCK_NANOSLEEP + struct timespec zero = {0, 0}; + ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &zero, NULL); + err = ret; +#elif defined(HAVE_NANOSLEEP) + struct timespec zero = {0, 0}; + ret = nanosleep(&zero, NULL); + err = errno; +#else // POSIX-compliant select(2) allows the 'timeout' parameter to // be modified but also mandates that the function should return // immediately if *both* structure's fields are zero (which is @@ -2447,13 +2437,15 @@ pysleep_zero_posix(void) // this is also the case for zero timeouts), we prefer supplying // a fresh timeout everytime. struct timeval zero = {0, 0}; - Py_BEGIN_ALLOW_THREADS ret = select(0, NULL, NULL, NULL, &zero); + err = errno; +#endif Py_END_ALLOW_THREADS if (ret == 0) { return 0; } - if (errno != EINTR) { + if (err != EINTR) { + errno = err; PyErr_SetFromErrno(PyExc_OSError); return -1; } From 153b326ffd7aea805275b8f93f5909f103a74196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 3 Jan 2025 09:59:45 +0100 Subject: [PATCH 17/26] update docs --- Doc/library/time.rst | 12 ------------ Doc/whatsnew/3.14.rst | 5 ++--- .../2024-12-26-13-09-25.gh-issue-125997.Vsibep.rst | 4 ++-- 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/Doc/library/time.rst b/Doc/library/time.rst index 7927e044cdfa3c..bc920981f6a87d 100644 --- a/Doc/library/time.rst +++ b/Doc/library/time.rst @@ -397,18 +397,10 @@ Functions .. rubric:: Unix implementation - If *secs* is zero, ``select()`` is used. Otherwise: - * Use ``clock_nanosleep()`` if available (resolution: 1 nanosecond); * Or use ``nanosleep()`` if available (resolution: 1 nanosecond); * Or use ``select()`` (resolution: 1 microsecond). - .. note:: - - To voluntarily relinquish the CPU, specify a read-time :ref:`scheduling - policy ` (see :manpage:`sched_yield(2)`) and use - :func:`os.sched_yield` instead. - .. audit-event:: time.sleep secs .. versionchanged:: 3.5 @@ -423,10 +415,6 @@ Functions .. versionchanged:: 3.13 Raises an auditing event. - .. versionchanged:: next - On Unix, ``time.sleep(0)`` always uses ``select()``, even if the - ``clock_nanosleep()`` or ``nanosleep()`` functions are available. - .. index:: single: % (percent); datetime format diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index f7628ae4896fcd..ce8545c96cc1bc 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -621,9 +621,8 @@ Two new events are added: :monitoring-event:`BRANCH_LEFT` and time ---- -* Ensure that :func:`time.sleep(0) ` does not accumulate delays on - POSIX platforms. The implementation always uses :manpage:`select(2)` even if - the :manpage:`clock_nanosleep` or :manpage:`nanosleep` functions are present. +* Ensure that :func:`time.sleep(0) ` does not degrade over time + on non-Windows platforms. (Contributed by Bénédikt Tran in :gh:`125997`.) diff --git a/Misc/NEWS.d/next/Library/2024-12-26-13-09-25.gh-issue-125997.Vsibep.rst b/Misc/NEWS.d/next/Library/2024-12-26-13-09-25.gh-issue-125997.Vsibep.rst index 88a3adb50914b7..70c7999444450c 100644 --- a/Misc/NEWS.d/next/Library/2024-12-26-13-09-25.gh-issue-125997.Vsibep.rst +++ b/Misc/NEWS.d/next/Library/2024-12-26-13-09-25.gh-issue-125997.Vsibep.rst @@ -1,2 +1,2 @@ -Ensure that :func:`time.sleep(0) ` does not accumulate delays on -non-Windows platforms. Patch by Bénédikt Tran. +Ensure that :func:`time.sleep(0) ` does not degrade over time +on non-Windows platforms. Patch by Bénédikt Tran. From 7e351c99f85c22e4ea57b014e41ff693fec552ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 3 Jan 2025 10:01:28 +0100 Subject: [PATCH 18/26] update tests --- Lib/test/test_time.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 83c0aead5fae71..5969e789a37f7b 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -180,7 +180,6 @@ def test_sleep_zero_posix(self): N2 = 100_000 # large number of samples for time.sleep(0) # Compute how long time.sleep() takes for the 'time' clock resolution. - # We expect the result to be around 50 us for a nanosecond resolution. eps = time.get_clock_info('time').resolution max_dt_ns = self.stat_for_test_sleep(N1, time.sleep, eps) From 285c54a63c30be9f482b6c9c3d50f90434d60e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 3 Jan 2025 10:02:06 +0100 Subject: [PATCH 19/26] revert un-necessary docs updates --- Doc/library/os.rst | 4 ---- Doc/library/time.rst | 4 +--- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 6d9ada309555d3..69e6192038ab2b 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -5411,8 +5411,6 @@ information, consult your Unix manpages. The following scheduling policies are exposed if they are supported by the operating system. -.. _os-scheduling-policy: - .. data:: SCHED_OTHER The default scheduling policy. @@ -5518,8 +5516,6 @@ operating system. Voluntarily relinquish the CPU. - See also :manpage:`sched_yield(2)`. - .. function:: sched_setaffinity(pid, mask, /) diff --git a/Doc/library/time.rst b/Doc/library/time.rst index bc920981f6a87d..6265c2214eaa0d 100644 --- a/Doc/library/time.rst +++ b/Doc/library/time.rst @@ -385,8 +385,6 @@ Functions The suspension time may be longer than requested by an arbitrary amount, because of the scheduling of other activity in the system. - .. rubric:: Windows implementation - 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 @@ -395,7 +393,7 @@ Functions `_ which provides resolution of 100 nanoseconds. If *secs* is zero, ``Sleep(0)`` is used. - .. rubric:: Unix implementation + Unix implementation: * Use ``clock_nanosleep()`` if available (resolution: 1 nanosecond); * Or use ``nanosleep()`` if available (resolution: 1 nanosecond); From 434da3145a6c69b23bb1577e666faf6e3cdbb028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 6 Jan 2025 11:33:30 +0100 Subject: [PATCH 20/26] remove performance tests --- Lib/test/test_time.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 5969e789a37f7b..9ecba8dffca67b 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -172,32 +172,6 @@ def test_sleep(self): with self.subTest(value=value): time.sleep(value) - @unittest.skipIf(support.MS_WINDOWS, 'test only for non-Windows platforms') - def test_sleep_zero_posix(self): - # Test that time.sleep(0) does not accumulate delays. - - N1 = 1000 # small number of samples for time.sleep(eps) with eps > 0 - N2 = 100_000 # large number of samples for time.sleep(0) - - # Compute how long time.sleep() takes for the 'time' clock resolution. - eps = time.get_clock_info('time').resolution - max_dt_ns = self.stat_for_test_sleep(N1, time.sleep, eps) - - # We expect a gap between time.sleep(0) and time.sleep(eps). - avg_dt_ns = self.stat_for_test_sleep(N2, time.sleep, 0) - self.assertLess(avg_dt_ns, max_dt_ns) - - @staticmethod - def stat_for_test_sleep(n_samples, func, *args, **kwargs): - """Compute the average (ns) time execution of func(*args, **kwargs).""" - samples = [] - for _ in range(int(n_samples)): - t0 = time.monotonic_ns() - func(*args, **kwargs) - t1 = time.monotonic_ns() - samples.append(t1 - t0) - return math.fsum(samples) / n_samples - def test_epoch(self): # bpo-43869: Make sure that Python use the same Epoch on all platforms: # January 1, 1970, 00:00:00 (UTC). From ff06516bfef7484795c28da777eedf596efc7724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 6 Jan 2025 11:39:46 +0100 Subject: [PATCH 21/26] implement platform compile-time dependent `time.sleep(0)` The implementations of `time.sleep(0)` on Windows and POSIX are now handled by the same function. Previously, `time.sleep(0)` on Windows was handled by `pysleep()` while on POSIX platforms, it was handled by `pysleep_zero_posix()`. --- Modules/timemodule.c | 43 ++++++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/Modules/timemodule.c b/Modules/timemodule.c index b54c62f1a66794..016fe3142c052a 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -72,9 +72,7 @@ module time /* Forward declarations */ static int pysleep(PyTime_t timeout); -#ifndef MS_WINDOWS -static int pysleep_zero_posix(void); // see gh-125997 -#endif +static int pysleep_zero(void); // see gh-125997 typedef struct { @@ -2219,7 +2217,14 @@ pysleep(PyTime_t timeout) assert(!PyErr_Occurred()); #ifndef MS_WINDOWS if (timeout == 0) { // gh-125997 - return pysleep_zero_posix(); + return pysleep_zero(); + } +#else + PyTime_t timeout_100ns = _PyTime_As100Nanoseconds(timeout, + _PyTime_ROUND_CEILING); + // Maintain Windows Sleep() semantics for time.sleep(0) + if (timeout_100ns == 0) { + return pysleep_zero(); } #endif @@ -2300,21 +2305,6 @@ pysleep(PyTime_t timeout) return 0; #else // MS_WINDOWS - PyTime_t timeout_100ns = _PyTime_As100Nanoseconds(timeout, - _PyTime_ROUND_CEILING); - - // Maintain Windows Sleep() semantics for time.sleep(0) - if (timeout_100ns == 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_100ns)); @@ -2401,7 +2391,6 @@ pysleep(PyTime_t timeout) } -#ifndef MS_WINDOWS // time.sleep(0) optimized implementation. // On error, raise an exception and return -1. // On success, return 0. @@ -2412,10 +2401,10 @@ pysleep(PyTime_t timeout) // skip some calls to `PyTime_Monotonic()` and other checks when the timeout // is zero. For details, see https://github.com/python/cpython/pull/128274. static int -pysleep_zero_posix(void) +pysleep_zero(void) { assert(!PyErr_Occurred()); - +#ifndef MS_WINDOWS int ret, err; Py_BEGIN_ALLOW_THREADS #ifdef HAVE_CLOCK_NANOSLEEP @@ -2453,6 +2442,14 @@ pysleep_zero_posix(void) if (PyErr_CheckSignals()) { return -1; } +#else + 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 +#endif return 0; } -#endif From 40d56f1d376b279bc7f14af6ebdf5c6113b660a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 7 Jan 2025 10:41:05 +0100 Subject: [PATCH 22/26] address Victor's review --- Doc/whatsnew/3.14.rst | 5 +++-- Modules/timemodule.c | 11 +++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index ce8545c96cc1bc..3c3a51496a46e3 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -621,8 +621,9 @@ Two new events are added: :monitoring-event:`BRANCH_LEFT` and time ---- -* Ensure that :func:`time.sleep(0) ` does not degrade over time - on non-Windows platforms. +* Ensure that the duration of :func:`time.sleep(0) ` is as small + as possible on non-Windows platforms when :manpage:`clock_nanosleep(2)` + or :manpage:`nanosleep(2)` are used to implement :func:`!time.sleep`. (Contributed by Bénédikt Tran in :gh:`125997`.) diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 016fe3142c052a..fdbcf3b75a13f9 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -2222,8 +2222,7 @@ pysleep(PyTime_t timeout) #else PyTime_t timeout_100ns = _PyTime_As100Nanoseconds(timeout, _PyTime_ROUND_CEILING); - // Maintain Windows Sleep() semantics for time.sleep(0) - if (timeout_100ns == 0) { + if (timeout_100ns == 0) { // gh-125997 return pysleep_zero(); } #endif @@ -2397,9 +2396,9 @@ pysleep(PyTime_t timeout) // // Rationale // --------- -// time.sleep(0) accumulates delays in the generic implementation, but we can -// skip some calls to `PyTime_Monotonic()` and other checks when the timeout -// is zero. For details, see https://github.com/python/cpython/pull/128274. +// time.sleep(0) is slower when using the generic implementation, but we make +// it faster than time.sleep(eps) for eps > 0 so to avoid some performance +// annoyance. For details, see https://github.com/python/cpython/pull/128274. static int pysleep_zero(void) { @@ -2442,7 +2441,7 @@ pysleep_zero(void) if (PyErr_CheckSignals()) { return -1; } -#else +#else // Windows implementation 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 From c4ce9cc7ecd13b4935c3d17bfab64f66143d992f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 7 Jan 2025 10:48:12 +0100 Subject: [PATCH 23/26] update blurb --- .../Library/2024-12-26-13-09-25.gh-issue-125997.Vsibep.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2024-12-26-13-09-25.gh-issue-125997.Vsibep.rst b/Misc/NEWS.d/next/Library/2024-12-26-13-09-25.gh-issue-125997.Vsibep.rst index 70c7999444450c..9f86c61f973510 100644 --- a/Misc/NEWS.d/next/Library/2024-12-26-13-09-25.gh-issue-125997.Vsibep.rst +++ b/Misc/NEWS.d/next/Library/2024-12-26-13-09-25.gh-issue-125997.Vsibep.rst @@ -1,2 +1,4 @@ -Ensure that :func:`time.sleep(0) ` does not degrade over time -on non-Windows platforms. Patch by Bénédikt Tran. +Ensure that the duration of :func:`time.sleep(0) ` is as small +as possible on non-Windows platforms when :manpage:`clock_nanosleep(2)` +or :manpage:`nanosleep(2)` are used to implement :func:`!time.sleep`. +Patch by Bénédikt Tran. From 54b6dded3bedb53bb2a44355b321121aaca5d7b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 7 Jan 2025 10:49:29 +0100 Subject: [PATCH 24/26] simplify `pysleep` --- Modules/timemodule.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Modules/timemodule.c b/Modules/timemodule.c index fdbcf3b75a13f9..8804e3a8ec43ac 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -2215,18 +2215,9 @@ pysleep(PyTime_t timeout) { assert(timeout >= 0); assert(!PyErr_Occurred()); -#ifndef MS_WINDOWS if (timeout == 0) { // gh-125997 return pysleep_zero(); } -#else - PyTime_t timeout_100ns = _PyTime_As100Nanoseconds(timeout, - _PyTime_ROUND_CEILING); - if (timeout_100ns == 0) { // gh-125997 - return pysleep_zero(); - } -#endif - #ifndef MS_WINDOWS #ifdef HAVE_CLOCK_NANOSLEEP struct timespec timeout_abs; @@ -2304,6 +2295,9 @@ pysleep(PyTime_t timeout) return 0; #else // MS_WINDOWS + PyTime_t timeout_100ns = _PyTime_As100Nanoseconds(timeout, + _PyTime_ROUND_CEILING); + assert(timeout_100ns > 0); LARGE_INTEGER relative_timeout; // No need to check for integer overflow, both types are signed assert(sizeof(relative_timeout) == sizeof(timeout_100ns)); From 405e473ce157f14d06e89b657026357ded08a35e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 7 Jan 2025 11:49:44 +0100 Subject: [PATCH 25/26] add newline for readability --- Modules/timemodule.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 8804e3a8ec43ac..7ec30148a8b997 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -2218,6 +2218,7 @@ pysleep(PyTime_t timeout) if (timeout == 0) { // gh-125997 return pysleep_zero(); } + #ifndef MS_WINDOWS #ifdef HAVE_CLOCK_NANOSLEEP struct timespec timeout_abs; From b8030459692da165ad8b72c677789c5cb7244af6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 7 Jan 2025 16:02:10 +0100 Subject: [PATCH 26/26] revert to a `select()` alternative Using `clock_nanosleep()` would always take more than 50 us. --- Doc/whatsnew/3.14.rst | 7 ++++--- .../2024-12-26-13-09-25.gh-issue-125997.Vsibep.rst | 8 ++++---- Modules/timemodule.c | 10 ---------- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 3c3a51496a46e3..79262ea0905979 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -621,9 +621,10 @@ Two new events are added: :monitoring-event:`BRANCH_LEFT` and time ---- -* Ensure that the duration of :func:`time.sleep(0) ` is as small - as possible on non-Windows platforms when :manpage:`clock_nanosleep(2)` - or :manpage:`nanosleep(2)` are used to implement :func:`!time.sleep`. +* Specialize :func:`time.sleep(0) ` on non-Windows platforms to + always use :manpage:`select(2)` even if the :manpage:`clock_nanosleep` or + :manpage:`nanosleep` functions are present as these functions would sleep + for much longer than what is actually needed. (Contributed by Bénédikt Tran in :gh:`125997`.) diff --git a/Misc/NEWS.d/next/Library/2024-12-26-13-09-25.gh-issue-125997.Vsibep.rst b/Misc/NEWS.d/next/Library/2024-12-26-13-09-25.gh-issue-125997.Vsibep.rst index 9f86c61f973510..b1feeb60f37930 100644 --- a/Misc/NEWS.d/next/Library/2024-12-26-13-09-25.gh-issue-125997.Vsibep.rst +++ b/Misc/NEWS.d/next/Library/2024-12-26-13-09-25.gh-issue-125997.Vsibep.rst @@ -1,4 +1,4 @@ -Ensure that the duration of :func:`time.sleep(0) ` is as small -as possible on non-Windows platforms when :manpage:`clock_nanosleep(2)` -or :manpage:`nanosleep(2)` are used to implement :func:`!time.sleep`. -Patch by Bénédikt Tran. +Specialize :func:`time.sleep(0) ` on non-Windows platforms to +always use :manpage:`select(2)` even if the :manpage:`clock_nanosleep` or +:manpage:`nanosleep` functions are present as these functions would sleep +for much longer than what is actually needed. Patch by Bénédikt Tran. diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 7ec30148a8b997..1d45c2054a4bd8 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -2401,15 +2401,6 @@ pysleep_zero(void) #ifndef MS_WINDOWS int ret, err; Py_BEGIN_ALLOW_THREADS -#ifdef HAVE_CLOCK_NANOSLEEP - struct timespec zero = {0, 0}; - ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &zero, NULL); - err = ret; -#elif defined(HAVE_NANOSLEEP) - struct timespec zero = {0, 0}; - ret = nanosleep(&zero, NULL); - err = errno; -#else // POSIX-compliant select(2) allows the 'timeout' parameter to // be modified but also mandates that the function should return // immediately if *both* structure's fields are zero (which is @@ -2422,7 +2413,6 @@ pysleep_zero(void) struct timeval zero = {0, 0}; ret = select(0, NULL, NULL, NULL, &zero); err = errno; -#endif Py_END_ALLOW_THREADS if (ret == 0) { return 0;