@@ -73,7 +73,7 @@ module time
7373/* Forward declarations */
7474static int pysleep (PyTime_t timeout );
7575#ifndef MS_WINDOWS
76- static int pysleep_zero_posix (void );
76+ static int pysleep_zero_posix (void ); // see gh-125997
7777#endif
7878
7979
@@ -2218,7 +2218,7 @@ pysleep(PyTime_t timeout)
22182218 assert (timeout >= 0 );
22192219 assert (!PyErr_Occurred ());
22202220#ifndef MS_WINDOWS
2221- if (timeout == 0 ) {
2221+ if (timeout == 0 ) { // gh-125997
22222222 return pysleep_zero_posix ();
22232223 }
22242224#endif
@@ -2408,35 +2408,25 @@ pysleep(PyTime_t timeout)
24082408//
24092409// Rationale
24102410// ---------
2411- // time.sleep(0) accumulates delays if we use nanosleep() or clock_nanosleep().
2412- // To avoid this pitfall, we may either use select(0, NULL, NULL, NULL, &zero)
2413- // or sched_yield(). The former is implementation-sensitive while the latter
2414- // would explicit relinquish the CPU but is more portable [1].
2415- //
2416- // While select() is less portable due to various implementation details
2417- // it is slightly faster [2]. In addition, implicitly calling the kernel's
2418- // algorithm in time.sleep(0) may not be what non-Windows users expect [3].
2419- //
2420- // Therefore, we opt for a solution based on select() instead of sched_yield().
2421- //
2422- // [1] On Linux, calling sched_yield() causes the kernel's scheduling algorithm
2423- // to run as well and could be inefficient in terms of CPU consumption if
2424- // time.sleep(0) is successively called multiple times.
2425- //
2426- // [2] Experimentally, the CPU consumption of a sched_yield() solution is
2427- // similar to that one based on select(), albeit slightly slower.
2428- //
2429- // [3] sched_yield() is not recommended when resources needed by other
2430- // schedulable threads are still held by the caller (which may be
2431- // the case) and using it with with nondeterministic scheduling policies
2432- // such as SCHED_OTHER (which is the default) results in an unspecified
2433- // behaviour.
2411+ // time.sleep(0) accumulates delays in the generic implementation, but we can
2412+ // skip some calls to `PyTime_Monotonic()` and other checks when the timeout
2413+ // is zero. For details, see https://github.com/python/cpython/pull/128274.
24342414static int
24352415pysleep_zero_posix (void )
24362416{
24372417 assert (!PyErr_Occurred ());
24382418
2439- int ret ;
2419+ int ret , err ;
2420+ Py_BEGIN_ALLOW_THREADS
2421+ #ifdef HAVE_CLOCK_NANOSLEEP
2422+ struct timespec zero = {0 , 0 };
2423+ ret = clock_nanosleep (CLOCK_MONOTONIC , TIMER_ABSTIME , & zero , NULL );
2424+ err = ret ;
2425+ #elif defined(HAVE_NANOSLEEP )
2426+ struct timespec zero = {0 , 0 };
2427+ ret = nanosleep (& zero , NULL );
2428+ err = errno ;
2429+ #else
24402430 // POSIX-compliant select(2) allows the 'timeout' parameter to
24412431 // be modified but also mandates that the function should return
24422432 // immediately if *both* structure's fields are zero (which is
@@ -2447,13 +2437,15 @@ pysleep_zero_posix(void)
24472437 // this is also the case for zero timeouts), we prefer supplying
24482438 // a fresh timeout everytime.
24492439 struct timeval zero = {0 , 0 };
2450- Py_BEGIN_ALLOW_THREADS
24512440 ret = select (0 , NULL , NULL , NULL , & zero );
2441+ err = errno ;
2442+ #endif
24522443 Py_END_ALLOW_THREADS
24532444 if (ret == 0 ) {
24542445 return 0 ;
24552446 }
2456- if (errno != EINTR ) {
2447+ if (err != EINTR ) {
2448+ errno = err ;
24572449 PyErr_SetFromErrno (PyExc_OSError );
24582450 return -1 ;
24592451 }
0 commit comments