-
-
Notifications
You must be signed in to change notification settings - Fork 32k
gh-125997: ensure that time.sleep(0)
is not delayed on non-Windows platforms
#128274
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 24 commits
3754e9e
3c0c4e6
c18f15f
8d270f2
2996937
80de853
c7aa428
f15436e
68e2988
afc6186
f6a817f
fb9eb41
ecb9636
1ed9877
23b4740
40155ff
153b326
7e351c9
285c54a
434da31
ff06516
40d56f1
c4ce9cc
54b6dde
405e473
b803045
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
Ensure that the duration of :func:`time.sleep(0) <time.sleep>` 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. |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -72,6 +72,7 @@ module time | |||||
|
||||||
/* Forward declarations */ | ||||||
static int pysleep(PyTime_t timeout); | ||||||
static int pysleep_zero(void); // see gh-125997 | ||||||
|
||||||
|
||||||
typedef struct { | ||||||
|
@@ -2213,7 +2214,10 @@ static int | |||||
pysleep(PyTime_t timeout) | ||||||
{ | ||||||
assert(timeout >= 0); | ||||||
|
||||||
assert(!PyErr_Occurred()); | ||||||
if (timeout == 0) { // gh-125997 | ||||||
return pysleep_zero(); | ||||||
} | ||||||
picnixz marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
#ifndef MS_WINDOWS | ||||||
#ifdef HAVE_CLOCK_NANOSLEEP | ||||||
struct timespec timeout_abs; | ||||||
|
@@ -2292,20 +2296,8 @@ 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; | ||||||
} | ||||||
|
||||||
_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)); | ||||||
|
@@ -2390,3 +2382,67 @@ pysleep(PyTime_t timeout) | |||||
return -1; | ||||||
#endif | ||||||
} | ||||||
|
||||||
|
||||||
// time.sleep(0) optimized implementation. | ||||||
// On error, raise an exception and return -1. | ||||||
// On success, return 0. | ||||||
picnixz marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
// | ||||||
// Rationale | ||||||
// --------- | ||||||
// 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) | ||||||
{ | ||||||
assert(!PyErr_Occurred()); | ||||||
#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); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think that TIMER_ABSTIME is appropriate here:
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I first thought about:
Without this flag, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm leaving for a few days but I'm still struggling to convince myself between the use of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I was wrong. See #128274 (comment). |
||||||
err = ret; | ||||||
#elif defined(HAVE_NANOSLEEP) | ||||||
struct timespec zero = {0, 0}; | ||||||
ret = nanosleep(&zero, NULL); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it worth it to have 3 code paths for sleep(0)? Can't we always use select()? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We had a long discussion on this matter but long story short:
|
||||||
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 | ||||||
// 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}; | ||||||
ret = select(0, NULL, NULL, NULL, &zero); | ||||||
err = errno; | ||||||
#endif | ||||||
Py_END_ALLOW_THREADS | ||||||
if (ret == 0) { | ||||||
return 0; | ||||||
} | ||||||
if (err != EINTR) { | ||||||
errno = err; | ||||||
PyErr_SetFromErrno(PyExc_OSError); | ||||||
return -1; | ||||||
} | ||||||
/* sleep was interrupted by SIGINT */ | ||||||
if (PyErr_CheckSignals()) { | ||||||
return -1; | ||||||
} | ||||||
#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 | ||||||
// other threads ready to run, the function returns immediately, and | ||||||
// the thread continues execution. | ||||||
Sleep(0); | ||||||
Py_END_ALLOW_THREADS | ||||||
#endif | ||||||
return 0; | ||||||
} |
Uh oh!
There was an error while loading. Please reload this page.