diff --git a/Lib/test/lock_tests.py b/Lib/test/lock_tests.py index 47025c041daf1e..5264b2ba5c5b70 100644 --- a/Lib/test/lock_tests.py +++ b/Lib/test/lock_tests.py @@ -772,6 +772,44 @@ def test_nan_timeout(self): self.assertRaises(ValueError, cond.wait_for, lambda: False, nan) self.assertEqual(len(cond._waiters), 0) + def test_notify_after_timeout(self): + # gh-160414: check that notify() still wakes a waiter when the one + # it picks has just timed out. + cond = self.condtype() + waiting = threading.Event() + notified = threading.Event() + results = [] + acquire_restore = cond._acquire_restore + def hold_t1(state): + if threading.current_thread() is t1: + waiting.set() + notified.wait(support.SHORT_TIMEOUT) + acquire_restore(state) + cond._acquire_restore = hold_t1 + def short(): + with cond: + results.append(("short", cond.wait(0.01))) + def long(): + waiting.wait() + with cond: + results.append(("long", cond.wait(support.SHORT_TIMEOUT))) + t1 = threading.Thread(target=short) + t2 = threading.Thread(target=long) + t1.start() + t2.start() + try: + waiting.wait() + for _ in support.sleeping_retry(support.SHORT_TIMEOUT): + with cond: + if len(cond._waiters) == 2: + cond.notify() + break + finally: + notified.set() + t1.join() + t2.join() + self.assertEqual(sorted(results), [("long", True), ("short", False)]) + def test_waitfor(self): cond = self.condtype() state = 0 diff --git a/Lib/threading.py b/Lib/threading.py index 507d40a1baa275..e2302df229a896 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -368,14 +368,18 @@ def wait(self, timeout=None): else: # A NaN timeout is rejected by acquire(). gotit = waiter.acquire(True, timeout) - return gotit finally: self._acquire_restore(saved_state) if not gotit: - try: - self._waiters.remove(waiter) - except ValueError: - pass + if waiter.acquire(False): + # gh-160414: notified after the timeout; pass it on. + self.notify() + else: + try: + self._waiters.remove(waiter) + except ValueError: + pass + return gotit def wait_for(self, predicate, timeout=None): """Wait until a condition evaluates to True. diff --git a/Misc/NEWS.d/next/Library/2026-10-23-09-46-09.gh-issue-160414.Vq8sLm.rst b/Misc/NEWS.d/next/Library/2026-10-23-09-46-09.gh-issue-160414.Vq8sLm.rst new file mode 100644 index 00000000000000..892b97eb52894e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-23-09-46-09.gh-issue-160414.Vq8sLm.rst @@ -0,0 +1,2 @@ +Fix :meth:`threading.Condition.notify` waking no thread when the waiter it +picks has just timed out; the notification now goes to the next waiter.