summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSam Gross <colesbury@gmail.com>2024-04-15 16:54:56 (GMT)
committerGitHub <noreply@github.com>2024-04-15 16:54:56 (GMT)
commit520cf2170ea08730e142d591e311b7ab8a6afe63 (patch)
tree7be14a1bffc20cd73b06b3b19ad85ca2246700fc
parent106e9ddc435372f3977432d76d0b1cb46ac72c5f (diff)
downloadcpython-520cf2170ea08730e142d591e311b7ab8a6afe63.zip
cpython-520cf2170ea08730e142d591e311b7ab8a6afe63.tar.gz
cpython-520cf2170ea08730e142d591e311b7ab8a6afe63.tar.bz2
gh-117688: Fix deadlock in test_no_stale_references with GIL disabled (#117720)
Check `my_object_collected.wait()` in a loop to give the main thread a chance to merge the reference count fields. Additionally, call `my_object_collected.set()` in a background thread to avoid deadlocking when the destructor is called asynchronously via the eval breaker within the body of of `my_object_collected.wait()`.
-rw-r--r--Lib/test/test_concurrent_futures/executor.py32
1 files changed, 21 insertions, 11 deletions
diff --git a/Lib/test/test_concurrent_futures/executor.py b/Lib/test/test_concurrent_futures/executor.py
index 6a79fe6..3049bb7 100644
--- a/Lib/test/test_concurrent_futures/executor.py
+++ b/Lib/test/test_concurrent_futures/executor.py
@@ -83,24 +83,34 @@ class ExecutorTest:
# references.
my_object = MyObject()
my_object_collected = threading.Event()
- my_object_callback = weakref.ref(
- my_object, lambda obj: my_object_collected.set())
- fut = self.executor.submit(my_object.my_method)
+ def set_event():
+ if Py_GIL_DISABLED:
+ # gh-117688 Avoid deadlock by setting the event in a
+ # background thread. The current thread may be in the middle
+ # of the my_object_collected.wait() call, which holds locks
+ # needed by my_object_collected.set().
+ threading.Thread(target=my_object_collected.set).start()
+ else:
+ my_object_collected.set()
+ my_object_callback = weakref.ref(my_object, lambda obj: set_event())
+ # Deliberately discarding the future.
+ self.executor.submit(my_object.my_method)
del my_object
if Py_GIL_DISABLED:
# Due to biased reference counting, my_object might only be
# deallocated while the thread that created it runs -- if the
# thread is paused waiting on an event, it may not merge the
- # refcount of the queued object. For that reason, we wait for the
- # task to finish (so that it's no longer referenced) and force a
- # GC to ensure that it is collected.
- fut.result() # Wait for the task to finish.
- support.gc_collect()
+ # refcount of the queued object. For that reason, we alternate
+ # between running the GC and waiting for the event.
+ wait_time = 0
+ collected = False
+ while not collected and wait_time <= support.SHORT_TIMEOUT:
+ support.gc_collect()
+ collected = my_object_collected.wait(timeout=1.0)
+ wait_time += 1.0
else:
- del fut # Deliberately discard the future.
-
- collected = my_object_collected.wait(timeout=support.SHORT_TIMEOUT)
+ collected = my_object_collected.wait(timeout=support.SHORT_TIMEOUT)
self.assertTrue(collected,
"Stale reference not collected within timeout.")