mirror of
https://github.com/python/cpython
synced 2026-09-27 04:08:17 +03:00
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]: |
||
|---|---|---|
| .. | ||
| clinic | ||
| deepfreeze | ||
| frozen_modules | ||
| _warnings.c | ||
| adaptive.md | ||
| asdl.c | ||
| asm_trampoline.S | ||
| assemble.c | ||
| ast.c | ||
| ast_opt.c | ||
| ast_unparse.c | ||
| bltinmodule.c | ||
| bootstrap_hash.c | ||
| brc.c | ||
| bytecodes.c | ||
| ceval.c | ||
| ceval_gil.c | ||
| ceval_macros.h | ||
| codecs.c | ||
| compile.c | ||
| condvar.h | ||
| context.c | ||
| critical_section.c | ||
| crossinterp.c | ||
| crossinterp_data_lookup.h | ||
| crossinterp_exceptions.h | ||
| dtoa.c | ||
| dup2.c | ||
| dynamic_annotations.c | ||
| dynload_hpux.c | ||
| dynload_shlib.c | ||
| dynload_stub.c | ||
| dynload_win.c | ||
| emscripten_signal.c | ||
| emscripten_trampoline.c | ||
| errors.c | ||
| executor_cases.c.h | ||
| fileutils.c | ||
| flowgraph.c | ||
| formatter_unicode.c | ||
| frame.c | ||
| frozen.c | ||
| frozenmain.c | ||
| future.c | ||
| gc.c | ||
| gc_free_threading.c | ||
| gc_gil.c | ||
| generated_cases.c.h | ||
| getargs.c | ||
| getcompiler.c | ||
| getcopyright.c | ||
| getopt.c | ||
| getplatform.c | ||
| getversion.c | ||
| hamt.c | ||
| hashtable.c | ||
| import.c | ||
| importdl.c | ||
| initconfig.c | ||
| instrumentation.c | ||
| intrinsics.c | ||
| jit.c | ||
| legacy_tracing.c | ||
| lock.c | ||
| marshal.c | ||
| modsupport.c | ||
| mysnprintf.c | ||
| mystrtoul.c | ||
| object_stack.c | ||
| opcode_targets.h | ||
| optimizer.c | ||
| optimizer_analysis.c | ||
| optimizer_bytecodes.c | ||
| optimizer_cases.c.h | ||
| optimizer_symbols.c | ||
| parking_lot.c | ||
| pathconfig.c | ||
| perf_trampoline.c | ||
| preconfig.c | ||
| pyarena.c | ||
| pyctype.c | ||
| pyfpe.c | ||
| pyhash.c | ||
| pylifecycle.c | ||
| pymath.c | ||
| pystate.c | ||
| pystrcmp.c | ||
| pystrhex.c | ||
| pystrtod.c | ||
| Python-ast.c | ||
| Python-tokenize.c | ||
| pythonrun.c | ||
| pytime.c | ||
| qsbr.c | ||
| README | ||
| specialize.c | ||
| stdlib_module_names.h | ||
| structmember.c | ||
| suggestions.c | ||
| symtable.c | ||
| sysmodule.c | ||
| thread.c | ||
| thread_nt.h | ||
| thread_pthread.h | ||
| thread_pthread_stubs.h | ||
| tier2_engine.md | ||
| traceback.c | ||
| tracemalloc.c | ||
| vm-state.md | ||
Miscellaneous source files for the main Python shared library