Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
bpo-41710: PyThread_acquire_lock_timed() clamps the timout
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().
  • Loading branch information
vstinner committed Sep 30, 2021
commit 8f791d9081bfb1c9abf104f01a9c49ec1039f908
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
The PyThread_acquire_lock_timed() function now clamps the timeout if it is
too large, rather than aborting the process. Patch by Victor Stinner.
8 changes: 7 additions & 1 deletion Python/thread_nt.h
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,13 @@ PyThread_acquire_lock_timed(PyThread_type_lock aLock,
if (microseconds % 1000 > 0)
++milliseconds;
if (milliseconds > PY_DWORD_MAX) {
Py_FatalError("Timeout larger than PY_TIMEOUT_MAX");
// bpo-41710: PyThread_acquire_lock_timed() cannot report timeout
// overflow to the caller, so clamp the timeout to
// [0, PY_DWORD_MAX] milliseconds.
//
// _thread.Lock.acquire() and _thread.RLock.acquire() raise an
// OverflowError if microseconds is greater than PY_TIMEOUT_MAX.
milliseconds = PY_DWORD_MAX;
}
}
else {
Expand Down
61 changes: 32 additions & 29 deletions Python/thread_pthread.h
Original file line number Diff line number Diff line change
Expand Up @@ -433,33 +433,45 @@ 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].
//
// _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 {
Expand All @@ -472,32 +484,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");
}
Expand Down