cpython/Lib/test/test_concurrent_futures
mpage 33da0e844c
gh-114271: Fix race in `Thread.join()` (#114839)
There is a race between when `Thread._tstate_lock` is released[^1] in `Thread._wait_for_tstate_lock()`
and when `Thread._stop()` asserts[^2] that it is unlocked. Consider the following execution
involving threads A, B, and C:

1. A starts.
2. B joins A, blocking on its `_tstate_lock`.
3. C joins A, blocking on its `_tstate_lock`.
4. A finishes and releases its `_tstate_lock`.
5. B acquires A's `_tstate_lock` in `_wait_for_tstate_lock()`, releases it, but is swapped
   out before calling `_stop()`.
6. C is scheduled, acquires A's `_tstate_lock` in `_wait_for_tstate_lock()` but is swapped
   out before releasing it.
7. B is scheduled, calls `_stop()`, which asserts that A's `_tstate_lock` is not held.
   However, C holds it, so the assertion fails.

The race can be reproduced[^3] by inserting sleeps at the appropriate points in
the threading code. To do so, run the `repro_join_race.py` from the linked repo.

There are two main parts to this PR:

1. `_tstate_lock` is replaced with an event that is attached to `PyThreadState`.
   The event is set by the runtime prior to the thread being cleared (in the same
   place that `_tstate_lock` was released). `Thread.join()` blocks waiting for the
   event to be set.
2. `_PyInterpreterState_WaitForThreads()` provides the ability to wait for all
   non-daemon threads to exit. To do so, an `is_daemon` predicate was added to
   `PyThreadState`. This field is set each time a thread is created. `threading._shutdown()`
   now calls into `_PyInterpreterState_WaitForThreads()` instead of waiting on
   `_tstate_lock`s.

[^1]: 441affc9e7/Lib/threading.py (L1201)
[^2]: 441affc9e7/Lib/threading.py (L1115)
[^3]: 8194653279

---------

Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
Co-authored-by: Antoine Pitrou <antoine@python.org>
2024-03-16 13:56:30 +01:00
..
__init__.py gh-71052: fix test_concurrent_futures wasi regression. (#115923) 2024-02-26 00:02:56 +00:00
executor.py gh-110481: Implement inter-thread queue for biased reference counting (#114824) 2024-02-09 17:08:32 -05:00
test_as_completed.py gh-109565: Fix concurrent.futures test_future_times_out() (#109949) 2023-09-27 08:51:44 +00:00
test_deadlock.py gh-109917: Fix test instability in test_concurrent_futures (#110306) 2023-10-03 22:59:49 +01:00
test_future.py
test_init.py gh-71052: Enable test_concurrent_futures on platforms that lack multiprocessing (gh-115917) 2024-02-25 11:38:18 -08:00
test_process_pool.py gh-114271: Fix race in `Thread.join()` (#114839) 2024-03-16 13:56:30 +01:00
test_shutdown.py gh-116682: stdout may be empty in test_cancel_futures_wait_false (#116683) 2024-03-12 20:11:58 -04:00
test_thread_pool.py gh-109649: Use os.process_cpu_count() (#110165) 2023-10-01 03:14:57 +02:00
test_wait.py gh-109594: Fix concurrent.futures test_timeout() (#110018) 2023-09-28 15:21:15 +02:00
util.py gh-112536: Add TSAN builds on Github Actions (#116872) 2024-03-16 11:10:37 +01:00