diff options
author | Victor Stinner <vstinner@python.org> | 2021-09-30 08:16:51 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-30 08:16:51 (GMT) |
commit | 37b8294d6295ca12553fd7c98778be71d24f4b24 (patch) | |
tree | 8d9d17fb410224326246dbb25d762feb9aae6053 /Python | |
parent | a1437170039dc2c07e6040d3a8ba8d91434b730d (diff) | |
download | cpython-37b8294d6295ca12553fd7c98778be71d24f4b24.zip cpython-37b8294d6295ca12553fd7c98778be71d24f4b24.tar.gz cpython-37b8294d6295ca12553fd7c98778be71d24f4b24.tar.bz2 |
bpo-41710: PyThread_acquire_lock_timed() clamps the timout (GH-28643)
PyThread_acquire_lock_timed() now clamps the timeout into the
[_PyTime_MIN; _PyTime_MAX] range (_PyTime_t type) if it is too large,
rather than calling Py_FatalError() which aborts the process.
PyThread_acquire_lock_timed() no longer uses
MICROSECONDS_TO_TIMESPEC() to compute sem_timedwait() argument, but
_PyTime_GetSystemClock() and _PyTime_AsTimespec_truncate().
Fix _thread.TIMEOUT_MAX value on Windows: the maximum timeout is
0x7FFFFFFF milliseconds (around 24.9 days), not 0xFFFFFFFF
milliseconds (around 49.7 days).
Set PY_TIMEOUT_MAX to 0x7FFFFFFF milliseconds, rather than 0xFFFFFFFF
milliseconds.
Fix PY_TIMEOUT_MAX overflow test: replace (us >= PY_TIMEOUT_MAX) with
(us > PY_TIMEOUT_MAX).
Diffstat (limited to 'Python')
-rw-r--r-- | Python/thread_nt.h | 22 | ||||
-rw-r--r-- | Python/thread_pthread.h | 63 |
2 files changed, 52 insertions, 33 deletions
diff --git a/Python/thread_nt.h b/Python/thread_nt.h index f8c098c..0beb3d3 100644 --- a/Python/thread_nt.h +++ b/Python/thread_nt.h @@ -292,6 +292,10 @@ PyThread_free_lock(PyThread_type_lock aLock) FreeNonRecursiveMutex(aLock) ; } +// WaitForSingleObject() documentation: "The time-out value needs to be a +// positive number between 0 and 0x7FFFFFFF." INFINITE is equal to 0xFFFFFFFF. +const DWORD TIMEOUT_MS_MAX = 0x7FFFFFFF; + /* * Return 1 on success if the lock was acquired * @@ -309,10 +313,20 @@ PyThread_acquire_lock_timed(PyThread_type_lock aLock, if (microseconds >= 0) { milliseconds = microseconds / 1000; - if (microseconds % 1000 > 0) - ++milliseconds; - if (milliseconds > PY_DWORD_MAX) { - Py_FatalError("Timeout larger than PY_TIMEOUT_MAX"); + // Round milliseconds away from zero + if (microseconds % 1000 > 0) { + milliseconds++; + } + if (milliseconds > (PY_TIMEOUT_T)TIMEOUT_MS_MAX) { + // bpo-41710: PyThread_acquire_lock_timed() cannot report timeout + // overflow to the caller, so clamp the timeout to + // [0, TIMEOUT_MS_MAX] milliseconds. + // + // TIMEOUT_MS_MAX milliseconds is around 24.9 days. + // + // _thread.Lock.acquire() and _thread.RLock.acquire() raise an + // OverflowError if microseconds is greater than PY_TIMEOUT_MAX. + milliseconds = TIMEOUT_MS_MAX; } } else { diff --git a/Python/thread_pthread.h b/Python/thread_pthread.h index 7f04151..3815ffa 100644 --- a/Python/thread_pthread.h +++ b/Python/thread_pthread.h @@ -433,33 +433,47 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds, PyLockStatus success; sem_t *thelock = (sem_t *)lock; int status, error = 0; - struct timespec ts; - _PyTime_t deadline = 0; (void) error; /* silence unused-but-set-variable warning */ dprintf(("PyThread_acquire_lock_timed(%p, %lld, %d) called\n", lock, microseconds, intr_flag)); - if (microseconds > PY_TIMEOUT_MAX) { - Py_FatalError("Timeout larger than PY_TIMEOUT_MAX"); + _PyTime_t timeout; + if (microseconds >= 0) { + _PyTime_t ns; + if (microseconds <= _PyTime_MAX / 1000) { + ns = microseconds * 1000; + } + else { + // bpo-41710: PyThread_acquire_lock_timed() cannot report timeout + // overflow to the caller, so clamp the timeout to + // [_PyTime_MIN, _PyTime_MAX]. + // + // _PyTime_MAX nanoseconds is around 292.3 years. + // + // _thread.Lock.acquire() and _thread.RLock.acquire() raise an + // OverflowError if microseconds is greater than PY_TIMEOUT_MAX. + ns = _PyTime_MAX; + } + timeout = _PyTime_FromNanoseconds(ns); + } + else { + timeout = _PyTime_FromNanoseconds(-1); } - if (microseconds > 0) { - MICROSECONDS_TO_TIMESPEC(microseconds, ts); - - if (!intr_flag) { - /* cannot overflow thanks to (microseconds > PY_TIMEOUT_MAX) - check done above */ - _PyTime_t timeout = _PyTime_FromNanoseconds(microseconds * 1000); - deadline = _PyTime_GetMonotonicClock() + timeout; - } + _PyTime_t deadline = 0; + if (timeout > 0 && !intr_flag) { + deadline = _PyTime_GetMonotonicClock() + timeout; } while (1) { - if (microseconds > 0) { + if (timeout > 0) { + _PyTime_t t = _PyTime_GetSystemClock() + timeout; + struct timespec ts; + _PyTime_AsTimespec_clamp(t, &ts); status = fix_status(sem_timedwait(thelock, &ts)); } - else if (microseconds == 0) { + else if (timeout == 0) { status = fix_status(sem_trywait(thelock)); } else { @@ -472,32 +486,23 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds, break; } - if (microseconds > 0) { + if (timeout > 0) { /* wait interrupted by a signal (EINTR): recompute the timeout */ - _PyTime_t dt = deadline - _PyTime_GetMonotonicClock(); - if (dt < 0) { + _PyTime_t timeout = deadline - _PyTime_GetMonotonicClock(); + if (timeout < 0) { status = ETIMEDOUT; break; } - else if (dt > 0) { - _PyTime_t realtime_deadline = _PyTime_GetSystemClock() + dt; - _PyTime_AsTimespec_clamp(realtime_deadline, &ts); - /* no need to update microseconds value, the code only care - if (microseconds > 0 or (microseconds == 0). */ - } - else { - microseconds = 0; - } } } /* Don't check the status if we're stopping because of an interrupt. */ if (!(intr_flag && status == EINTR)) { - if (microseconds > 0) { + if (timeout > 0) { if (status != ETIMEDOUT) CHECK_STATUS("sem_timedwait"); } - else if (microseconds == 0) { + else if (timeout == 0) { if (status != EAGAIN) CHECK_STATUS("sem_trywait"); } |