bpo-30768: Recompute timeout on interrupted lock#4103
bpo-30768: Recompute timeout on interrupted lock#4103vstinner merged 7 commits intopython:masterfrom vstinner:sem_timedwait
Conversation
Fix the pthread+semaphore implementation of PyThread_acquire_lock_timed() when called with timeout > 0 and intr_flag=0: recompute the timeout if sem_timedwait() is interrupted by a signal (EINTR). See also the PEP 475.
| if (microseconds > 0) | ||
|
|
||
| if (!intr_flag) { | ||
| _PyTime_t timeout = _PyTime_FromNanoseconds(microseconds * 1000); |
There was a problem hiding this comment.
What if microseconds * 1000 overflows?
There was a problem hiding this comment.
I added _PyTime_FromMicroseconds() to detect overflow on "us * 1000". But I chose to not detect overflow on "_PyTime_GetMonotonicClock() + timeout".
Python/pytime.c
Outdated
There was a problem hiding this comment.
I don't think it will be safe to raise an exception from a PyThread function (which could be called without the GIL).
There was a problem hiding this comment.
Oops, you're right. I forgot that.
Python/pytime.c
Outdated
There was a problem hiding this comment.
Isn't it too late? You've already raised the exception.
There was a problem hiding this comment.
I removed _PyTime_FromMicroseconds(), it was a bad idea.
|
I read again the code. PyThread_acquire_lock_timed() doc is explicit about the timeout limit: "microseconds must be less than PY_TIMEOUT_MAX. Behaviour otherwise is undefined." I removed _PyTime_FromMicroseconds(), it was a bad idea. Instead, I ajusted PY_TIMEOUT_MAX. By the way, the main consumer of PyThread_acquire_lock_timed() is threading.Lock.acquire() and this function is careful on integer overflow. This function already stores internally the timeout as a _PyTime_t and explicitely ensures that "microseconds <= PY_TIMEOUT_MAX". |
|
My patch doesn't change the value of threading.TIMEOUT_MAX, it's still a value larger than 292 years :-) If you ignore PyThread_acquire_lock_timed() doc and pass a timeout larger than 292 years, you get an overflow. Sorry for you. But why do you use such crazy timeout? :-) |
The pthread implementation of PyThread_acquire_lock() now fails with a fatal error if the timeout is larger than PY_TIMEOUT_MAX, as done in the Windows implementation. The check prevents any risk of overflow in PyThread_acquire_lock(). Add also PY_DWORD_MAX constant.
| } | ||
| else if (dt > 0) { | ||
| _PyTime_t realtime_deadline = _PyTime_GetSystemClock() + dt; | ||
| if (_PyTime_AsTimespec(realtime_deadline, &ts) < 0) { |
There was a problem hiding this comment.
Oh, this function raises an exception on overflow. This bug is now fixed in the new commit.
There was a problem hiding this comment.
4th commit of this PR, commit 7c428b7 which added the following code at the top of the function:
+ if (microseconds > PY_TIMEOUT_MAX) {
+ Py_FatalError("Timeout larger than PY_TIMEOUT_MAX");
+ }
There was a problem hiding this comment.
Ok, thank you for adding the comments :-)
I see, thank you. I guess it's large enough :-) |
| # define PY_TIMEOUT_MAX (PY_LLONG_MAX / 1000) | ||
| #elif defined (NT_THREADS) | ||
| /* In the NT API, the timeout is a DWORD and is expressed in milliseconds */ | ||
| # if 0xFFFFFFFFLL * 1000 < PY_LLONG_MAX |
There was a problem hiding this comment.
Which integer width does the C preprocessor use? :-) Can 0xFFFFFFFFLL * 1000 be truncated?
There was a problem hiding this comment.
I didn't write this code, I only moved it.
The "LL" suffix is for "long long" which should be at least 64-bit signed integer, no?
Are you suggesting to replace the code with "0xFFFFFFFFLL < PY_LLONG_MAX / 1000" to prevent any risk of integer overflow?
|
Oh, I forgot to regenerate generated file using "make clinic" when I added the PY_DWORD_MAX constant. It's now fixed. I also merged master into this PR. |
|
Hmm... I think this PR looks ok now. |
|
I tested manually the PR using files attached to the issue: "Apply PR 4103, apply attached test.patch, recompile Python, and run interrupted_lock.py to test the PR." It works well. Result: I modified interrupted_lock.py with: |
Fix the pthread implementation of PyThread_acquire_lock_timed() when
called with timeout > 0 and intr_flag=0: recompute the timeout if
sem_timedwait() is interrupted by a signal (EINTR).
See also the PEP 475.
https://bugs.python.org/issue30768