Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
38 changes: 38 additions & 0 deletions Lib/test/lock_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 9 additions & 5 deletions Lib/threading.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Loading