mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +00:00 
			
		
		
		
	gh-111964: Implement stop-the-world pauses (gh-112471)
The `--disable-gil` builds occasionally need to pause all but one thread. Some examples include: * Cyclic garbage collection, where this is often called a "stop the world event" * Before calling `fork()`, to ensure a consistent state for internal data structures * During interpreter shutdown, to ensure that daemon threads aren't accessing Python objects This adds the following functions to implement global and per-interpreter pauses: * `_PyEval_StopTheWorldAll()` and `_PyEval_StartTheWorldAll()` (for the global runtime) * `_PyEval_StopTheWorld()` and `_PyEval_StartTheWorld()` (per-interpreter) (The function names may change.) These functions are no-ops outside of the `--disable-gil` build.
This commit is contained in:
		
							parent
							
								
									5f1997896d
								
							
						
					
					
						commit
						441affc9e7
					
				
					 10 changed files with 336 additions and 29 deletions
				
			
		|  | @ -102,7 +102,7 @@ struct _ts { | ||||||
| #endif | #endif | ||||||
|     int _whence; |     int _whence; | ||||||
| 
 | 
 | ||||||
|     /* Thread state (_Py_THREAD_ATTACHED, _Py_THREAD_DETACHED, _Py_THREAD_GC).
 |     /* Thread state (_Py_THREAD_ATTACHED, _Py_THREAD_DETACHED, _Py_THREAD_SUSPENDED).
 | ||||||
|        See Include/internal/pycore_pystate.h for more details. */ |        See Include/internal/pycore_pystate.h for more details. */ | ||||||
|     int state; |     int state; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -205,6 +205,7 @@ void _PyEval_FrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame *frame) | ||||||
| #define _PY_CALLS_TO_DO_BIT 2 | #define _PY_CALLS_TO_DO_BIT 2 | ||||||
| #define _PY_ASYNC_EXCEPTION_BIT 3 | #define _PY_ASYNC_EXCEPTION_BIT 3 | ||||||
| #define _PY_GC_SCHEDULED_BIT 4 | #define _PY_GC_SCHEDULED_BIT 4 | ||||||
|  | #define _PY_EVAL_PLEASE_STOP_BIT 5 | ||||||
| 
 | 
 | ||||||
| /* Reserve a few bits for future use */ | /* Reserve a few bits for future use */ | ||||||
| #define _PY_EVAL_EVENTS_BITS 8 | #define _PY_EVAL_EVENTS_BITS 8 | ||||||
|  |  | ||||||
|  | @ -41,6 +41,22 @@ struct _Py_long_state { | ||||||
|     int max_str_digits; |     int max_str_digits; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | // Support for stop-the-world events. This exists in both the PyRuntime struct
 | ||||||
|  | // for global pauses and in each PyInterpreterState for per-interpreter pauses.
 | ||||||
|  | struct _stoptheworld_state { | ||||||
|  |     PyMutex mutex;       // Serializes stop-the-world attempts.
 | ||||||
|  | 
 | ||||||
|  |     // NOTE: The below fields are protected by HEAD_LOCK(runtime), not by the
 | ||||||
|  |     // above mutex.
 | ||||||
|  |     bool requested;      // Set when a pause is requested.
 | ||||||
|  |     bool world_stopped;  // Set when the world is stopped.
 | ||||||
|  |     bool is_global;      // Set when contained in PyRuntime struct.
 | ||||||
|  | 
 | ||||||
|  |     PyEvent stop_event;  // Set when thread_countdown reaches zero.
 | ||||||
|  |     Py_ssize_t thread_countdown;  // Number of threads that must pause.
 | ||||||
|  | 
 | ||||||
|  |     PyThreadState *requester; // Thread that requested the pause (may be NULL).
 | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| /* cross-interpreter data registry */ | /* cross-interpreter data registry */ | ||||||
| 
 | 
 | ||||||
|  | @ -166,6 +182,7 @@ struct _is { | ||||||
| 
 | 
 | ||||||
|     struct _warnings_runtime_state warnings; |     struct _warnings_runtime_state warnings; | ||||||
|     struct atexit_state atexit; |     struct atexit_state atexit; | ||||||
|  |     struct _stoptheworld_state stoptheworld; | ||||||
| 
 | 
 | ||||||
| #if defined(Py_GIL_DISABLED) | #if defined(Py_GIL_DISABLED) | ||||||
|     struct _mimalloc_interp_state mimalloc; |     struct _mimalloc_interp_state mimalloc; | ||||||
|  |  | ||||||
|  | @ -37,8 +37,7 @@ struct llist_node { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // Get the struct containing a node.
 | // Get the struct containing a node.
 | ||||||
| #define llist_data(node, type, member) \ | #define llist_data(node, type, member) (_Py_CONTAINER_OF(node, type, member)) | ||||||
|     (type*)((char*)node - offsetof(type, member)) |  | ||||||
| 
 | 
 | ||||||
| // Iterate over a list.
 | // Iterate over a list.
 | ||||||
| #define llist_for_each(node, head) \ | #define llist_for_each(node, head) \ | ||||||
|  |  | ||||||
|  | @ -21,23 +21,27 @@ extern "C" { | ||||||
| // interpreter at the same time. Only the "bound" thread may perform the
 | // interpreter at the same time. Only the "bound" thread may perform the
 | ||||||
| // transitions between "attached" and "detached" on its own PyThreadState.
 | // transitions between "attached" and "detached" on its own PyThreadState.
 | ||||||
| //
 | //
 | ||||||
| // The "gc" state is used to implement stop-the-world pauses, such as for
 | // The "suspended" state is used to implement stop-the-world pauses, such as
 | ||||||
| // cyclic garbage collection. It is only used in `--disable-gil` builds. It is
 | // for cyclic garbage collection. It is only used in `--disable-gil` builds.
 | ||||||
| // similar to the "detached" state, but only the thread performing a
 | // The "suspended" state is similar to the "detached" state in that in both
 | ||||||
| // stop-the-world pause may transition threads between the "detached" and "gc"
 | // states the thread is not allowed to call most Python APIs. However, unlike
 | ||||||
| // states. A thread trying to "attach" from the "gc" state will block until
 | // the "detached" state, a thread may not transition itself out from the
 | ||||||
| // it is transitioned back to "detached" when the stop-the-world pause is
 | // "suspended" state. Only the thread performing a stop-the-world pause may
 | ||||||
| // complete.
 | // transition a thread from the "suspended" state back to the "detached" state.
 | ||||||
| //
 | //
 | ||||||
| // State transition diagram:
 | // State transition diagram:
 | ||||||
| //
 | //
 | ||||||
| //            (bound thread)        (stop-the-world thread)
 | //            (bound thread)        (stop-the-world thread)
 | ||||||
| // [attached]       <->       [detached]       <->       [gc]
 | // [attached]       <->       [detached]       <->       [suspended]
 | ||||||
|  | //   |                                                        ^
 | ||||||
|  | //   +---------------------------->---------------------------+
 | ||||||
|  | //                          (bound thread)
 | ||||||
| //
 | //
 | ||||||
| // See `_PyThreadState_Attach()` and `_PyThreadState_Detach()`.
 | // The (bound thread) and (stop-the-world thread) labels indicate which thread
 | ||||||
|  | // is allowed to perform the transition.
 | ||||||
| #define _Py_THREAD_DETACHED     0 | #define _Py_THREAD_DETACHED     0 | ||||||
| #define _Py_THREAD_ATTACHED     1 | #define _Py_THREAD_ATTACHED     1 | ||||||
| #define _Py_THREAD_GC           2 | #define _Py_THREAD_SUSPENDED    2 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| /* Check if the current thread is the main thread.
 | /* Check if the current thread is the main thread.
 | ||||||
|  | @ -140,13 +144,36 @@ _PyThreadState_GET(void) | ||||||
| //
 | //
 | ||||||
| // High-level code should generally call PyEval_RestoreThread() instead, which
 | // High-level code should generally call PyEval_RestoreThread() instead, which
 | ||||||
| // calls this function.
 | // calls this function.
 | ||||||
| void _PyThreadState_Attach(PyThreadState *tstate); | extern void _PyThreadState_Attach(PyThreadState *tstate); | ||||||
| 
 | 
 | ||||||
| // Detaches the current thread from the interpreter.
 | // Detaches the current thread from the interpreter.
 | ||||||
| //
 | //
 | ||||||
| // High-level code should generally call PyEval_SaveThread() instead, which
 | // High-level code should generally call PyEval_SaveThread() instead, which
 | ||||||
| // calls this function.
 | // calls this function.
 | ||||||
| void _PyThreadState_Detach(PyThreadState *tstate); | extern void _PyThreadState_Detach(PyThreadState *tstate); | ||||||
|  | 
 | ||||||
|  | // Detaches the current thread to the "suspended" state if a stop-the-world
 | ||||||
|  | // pause is in progress.
 | ||||||
|  | //
 | ||||||
|  | // If there is no stop-the-world pause in progress, then the thread switches
 | ||||||
|  | // to the "detached" state.
 | ||||||
|  | extern void _PyThreadState_Suspend(PyThreadState *tstate); | ||||||
|  | 
 | ||||||
|  | // Perform a stop-the-world pause for all threads in the all interpreters.
 | ||||||
|  | //
 | ||||||
|  | // Threads in the "attached" state are paused and transitioned to the "GC"
 | ||||||
|  | // state. Threads in the "detached" state switch to the "GC" state, preventing
 | ||||||
|  | // them from reattaching until the stop-the-world pause is complete.
 | ||||||
|  | //
 | ||||||
|  | // NOTE: This is a no-op outside of Py_GIL_DISABLED builds.
 | ||||||
|  | extern void _PyEval_StopTheWorldAll(_PyRuntimeState *runtime); | ||||||
|  | extern void _PyEval_StartTheWorldAll(_PyRuntimeState *runtime); | ||||||
|  | 
 | ||||||
|  | // Perform a stop-the-world pause for threads in the specified interpreter.
 | ||||||
|  | //
 | ||||||
|  | // NOTE: This is a no-op outside of Py_GIL_DISABLED builds.
 | ||||||
|  | extern void _PyEval_StopTheWorld(PyInterpreterState *interp); | ||||||
|  | extern void _PyEval_StartTheWorld(PyInterpreterState *interp); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| static inline void | static inline void | ||||||
|  |  | ||||||
|  | @ -227,6 +227,13 @@ typedef struct pyruntimestate { | ||||||
|     struct _faulthandler_runtime_state faulthandler; |     struct _faulthandler_runtime_state faulthandler; | ||||||
|     struct _tracemalloc_runtime_state tracemalloc; |     struct _tracemalloc_runtime_state tracemalloc; | ||||||
| 
 | 
 | ||||||
|  |     // The rwmutex is used to prevent overlapping global and per-interpreter
 | ||||||
|  |     // stop-the-world events. Global stop-the-world events lock the mutex
 | ||||||
|  |     // exclusively (as a "writer"), while per-interpreter stop-the-world events
 | ||||||
|  |     // lock it non-exclusively (as "readers").
 | ||||||
|  |     _PyRWMutex stoptheworld_mutex; | ||||||
|  |     struct _stoptheworld_state stoptheworld; | ||||||
|  | 
 | ||||||
|     PyPreConfig preconfig; |     PyPreConfig preconfig; | ||||||
| 
 | 
 | ||||||
|     // Audit values must be preserved when Py_Initialize()/Py_Finalize()
 |     // Audit values must be preserved when Py_Initialize()/Py_Finalize()
 | ||||||
|  |  | ||||||
|  | @ -116,6 +116,9 @@ extern PyTypeObject _PyExc_MemoryError; | ||||||
|         }, \ |         }, \ | ||||||
|         .faulthandler = _faulthandler_runtime_state_INIT, \ |         .faulthandler = _faulthandler_runtime_state_INIT, \ | ||||||
|         .tracemalloc = _tracemalloc_runtime_state_INIT, \ |         .tracemalloc = _tracemalloc_runtime_state_INIT, \ | ||||||
|  |         .stoptheworld = { \ | ||||||
|  |             .is_global = 1, \ | ||||||
|  |         }, \ | ||||||
|         .float_state = { \ |         .float_state = { \ | ||||||
|             .float_format = _py_float_format_unknown, \ |             .float_format = _py_float_format_unknown, \ | ||||||
|             .double_format = _py_float_format_unknown, \ |             .double_format = _py_float_format_unknown, \ | ||||||
|  |  | ||||||
|  | @ -160,6 +160,9 @@ | ||||||
|     Py_FatalError("Unreachable C code path reached") |     Py_FatalError("Unreachable C code path reached") | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|  | #define _Py_CONTAINER_OF(ptr, type, member) \ | ||||||
|  |     (type*)((char*)ptr - offsetof(type, member)) | ||||||
|  | 
 | ||||||
| // Prevent using an expression as a l-value.
 | // Prevent using an expression as a l-value.
 | ||||||
| // For example, "int x; _Py_RVALUE(x) = 1;" fails with a compiler error.
 | // For example, "int x; _Py_RVALUE(x) = 1;" fails with a compiler error.
 | ||||||
| #define _Py_RVALUE(EXPR) ((void)0, (EXPR)) | #define _Py_RVALUE(EXPR) ((void)0, (EXPR)) | ||||||
|  |  | ||||||
|  | @ -949,6 +949,15 @@ _Py_HandlePending(PyThreadState *tstate) | ||||||
| { | { | ||||||
|     PyInterpreterState *interp = tstate->interp; |     PyInterpreterState *interp = tstate->interp; | ||||||
| 
 | 
 | ||||||
|  |     /* Stop-the-world */ | ||||||
|  |     if (_Py_eval_breaker_bit_is_set(interp, _PY_EVAL_PLEASE_STOP_BIT)) { | ||||||
|  |         _Py_set_eval_breaker_bit(interp, _PY_EVAL_PLEASE_STOP_BIT, 0); | ||||||
|  |         _PyThreadState_Suspend(tstate); | ||||||
|  | 
 | ||||||
|  |         /* The attach blocks until the stop-the-world event is complete. */ | ||||||
|  |         _PyThreadState_Attach(tstate); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /* Pending signals */ |     /* Pending signals */ | ||||||
|     if (_Py_eval_breaker_bit_is_set(interp, _PY_SIGNALS_PENDING_BIT)) { |     if (_Py_eval_breaker_bit_is_set(interp, _PY_SIGNALS_PENDING_BIT)) { | ||||||
|         if (handle_signals(tstate) != 0) { |         if (handle_signals(tstate) != 0) { | ||||||
|  |  | ||||||
							
								
								
									
										267
									
								
								Python/pystate.c
									
										
									
									
									
								
							
							
						
						
									
										267
									
								
								Python/pystate.c
									
										
									
									
									
								
							|  | @ -1336,6 +1336,11 @@ init_threadstate(_PyThreadStateImpl *_tstate, | ||||||
|     tstate->datastack_limit = NULL; |     tstate->datastack_limit = NULL; | ||||||
|     tstate->what_event = -1; |     tstate->what_event = -1; | ||||||
| 
 | 
 | ||||||
|  |     if (interp->stoptheworld.requested || _PyRuntime.stoptheworld.requested) { | ||||||
|  |         // Start in the suspended state if there is an ongoing stop-the-world.
 | ||||||
|  |         tstate->state = _Py_THREAD_SUSPENDED; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     tstate->_status.initialized = 1; |     tstate->_status.initialized = 1; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -1562,6 +1567,9 @@ PyThreadState_Clear(PyThreadState *tstate) | ||||||
|     // XXX Do it as early in the function as possible.
 |     // XXX Do it as early in the function as possible.
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void | ||||||
|  | decrement_stoptheworld_countdown(struct _stoptheworld_state *stw); | ||||||
|  | 
 | ||||||
| /* Common code for PyThreadState_Delete() and PyThreadState_DeleteCurrent() */ | /* Common code for PyThreadState_Delete() and PyThreadState_DeleteCurrent() */ | ||||||
| static void | static void | ||||||
| tstate_delete_common(PyThreadState *tstate) | tstate_delete_common(PyThreadState *tstate) | ||||||
|  | @ -1585,6 +1593,16 @@ tstate_delete_common(PyThreadState *tstate) | ||||||
|     if (tstate->next) { |     if (tstate->next) { | ||||||
|         tstate->next->prev = tstate->prev; |         tstate->next->prev = tstate->prev; | ||||||
|     } |     } | ||||||
|  |     if (tstate->state != _Py_THREAD_SUSPENDED) { | ||||||
|  |         // Any ongoing stop-the-world request should not wait for us because
 | ||||||
|  |         // our thread is getting deleted.
 | ||||||
|  |         if (interp->stoptheworld.requested) { | ||||||
|  |             decrement_stoptheworld_countdown(&interp->stoptheworld); | ||||||
|  |         } | ||||||
|  |         if (runtime->stoptheworld.requested) { | ||||||
|  |             decrement_stoptheworld_countdown(&runtime->stoptheworld); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|     HEAD_UNLOCK(runtime); |     HEAD_UNLOCK(runtime); | ||||||
| 
 | 
 | ||||||
|     // XXX Unbind in PyThreadState_Clear(), or earlier
 |     // XXX Unbind in PyThreadState_Clear(), or earlier
 | ||||||
|  | @ -1790,13 +1808,9 @@ tstate_try_attach(PyThreadState *tstate) | ||||||
| { | { | ||||||
| #ifdef Py_GIL_DISABLED | #ifdef Py_GIL_DISABLED | ||||||
|     int expected = _Py_THREAD_DETACHED; |     int expected = _Py_THREAD_DETACHED; | ||||||
|     if (_Py_atomic_compare_exchange_int( |     return _Py_atomic_compare_exchange_int(&tstate->state, | ||||||
|             &tstate->state, |  | ||||||
|                                            &expected, |                                            &expected, | ||||||
|             _Py_THREAD_ATTACHED)) { |                                            _Py_THREAD_ATTACHED); | ||||||
|         return 1; |  | ||||||
|     } |  | ||||||
|     return 0; |  | ||||||
| #else | #else | ||||||
|     assert(tstate->state == _Py_THREAD_DETACHED); |     assert(tstate->state == _Py_THREAD_DETACHED); | ||||||
|     tstate->state = _Py_THREAD_ATTACHED; |     tstate->state = _Py_THREAD_ATTACHED; | ||||||
|  | @ -1815,6 +1829,20 @@ tstate_set_detached(PyThreadState *tstate) | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void | ||||||
|  | tstate_wait_attach(PyThreadState *tstate) | ||||||
|  | { | ||||||
|  |     do { | ||||||
|  |         int expected = _Py_THREAD_SUSPENDED; | ||||||
|  | 
 | ||||||
|  |         // Wait until we're switched out of SUSPENDED to DETACHED.
 | ||||||
|  |         _PyParkingLot_Park(&tstate->state, &expected, sizeof(tstate->state), | ||||||
|  |                            /*timeout=*/-1, NULL, /*detach=*/0); | ||||||
|  | 
 | ||||||
|  |         // Once we're back in DETACHED we can re-attach
 | ||||||
|  |     } while (!tstate_try_attach(tstate)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void | void | ||||||
| _PyThreadState_Attach(PyThreadState *tstate) | _PyThreadState_Attach(PyThreadState *tstate) | ||||||
| { | { | ||||||
|  | @ -1836,10 +1864,7 @@ _PyThreadState_Attach(PyThreadState *tstate) | ||||||
|     tstate_activate(tstate); |     tstate_activate(tstate); | ||||||
| 
 | 
 | ||||||
|     if (!tstate_try_attach(tstate)) { |     if (!tstate_try_attach(tstate)) { | ||||||
|         // TODO: Once stop-the-world GC is implemented for --disable-gil builds
 |         tstate_wait_attach(tstate); | ||||||
|         // this will need to wait until the GC completes. For now, this case
 |  | ||||||
|         // should never happen.
 |  | ||||||
|         Py_FatalError("thread attach failed"); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Resume previous critical section. This acquires the lock(s) from the
 |     // Resume previous critical section. This acquires the lock(s) from the
 | ||||||
|  | @ -1853,8 +1878,8 @@ _PyThreadState_Attach(PyThreadState *tstate) | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void | static void | ||||||
| _PyThreadState_Detach(PyThreadState *tstate) | detach_thread(PyThreadState *tstate, int detached_state) | ||||||
| { | { | ||||||
|     // XXX assert(tstate_is_alive(tstate) && tstate_is_bound(tstate));
 |     // XXX assert(tstate_is_alive(tstate) && tstate_is_bound(tstate));
 | ||||||
|     assert(tstate->state == _Py_THREAD_ATTACHED); |     assert(tstate->state == _Py_THREAD_ATTACHED); | ||||||
|  | @ -1862,12 +1887,228 @@ _PyThreadState_Detach(PyThreadState *tstate) | ||||||
|     if (tstate->critical_section != 0) { |     if (tstate->critical_section != 0) { | ||||||
|         _PyCriticalSection_SuspendAll(tstate); |         _PyCriticalSection_SuspendAll(tstate); | ||||||
|     } |     } | ||||||
|     tstate_set_detached(tstate); |  | ||||||
|     tstate_deactivate(tstate); |     tstate_deactivate(tstate); | ||||||
|  |     tstate_set_detached(tstate); | ||||||
|     current_fast_clear(&_PyRuntime); |     current_fast_clear(&_PyRuntime); | ||||||
|     _PyEval_ReleaseLock(tstate->interp, tstate); |     _PyEval_ReleaseLock(tstate->interp, tstate); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void | ||||||
|  | _PyThreadState_Detach(PyThreadState *tstate) | ||||||
|  | { | ||||||
|  |     detach_thread(tstate, _Py_THREAD_DETACHED); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void | ||||||
|  | _PyThreadState_Suspend(PyThreadState *tstate) | ||||||
|  | { | ||||||
|  |     _PyRuntimeState *runtime = &_PyRuntime; | ||||||
|  | 
 | ||||||
|  |     assert(tstate->state == _Py_THREAD_ATTACHED); | ||||||
|  | 
 | ||||||
|  |     struct _stoptheworld_state *stw = NULL; | ||||||
|  |     HEAD_LOCK(runtime); | ||||||
|  |     if (runtime->stoptheworld.requested) { | ||||||
|  |         stw = &runtime->stoptheworld; | ||||||
|  |     } | ||||||
|  |     else if (tstate->interp->stoptheworld.requested) { | ||||||
|  |         stw = &tstate->interp->stoptheworld; | ||||||
|  |     } | ||||||
|  |     HEAD_UNLOCK(runtime); | ||||||
|  | 
 | ||||||
|  |     if (stw == NULL) { | ||||||
|  |         // Switch directly to "detached" if there is no active stop-the-world
 | ||||||
|  |         // request.
 | ||||||
|  |         detach_thread(tstate, _Py_THREAD_DETACHED); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Switch to "suspended" state.
 | ||||||
|  |     detach_thread(tstate, _Py_THREAD_SUSPENDED); | ||||||
|  | 
 | ||||||
|  |     // Decrease the count of remaining threads needing to park.
 | ||||||
|  |     HEAD_LOCK(runtime); | ||||||
|  |     decrement_stoptheworld_countdown(stw); | ||||||
|  |     HEAD_UNLOCK(runtime); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Decrease stop-the-world counter of remaining number of threads that need to
 | ||||||
|  | // pause. If we are the final thread to pause, notify the requesting thread.
 | ||||||
|  | static void | ||||||
|  | decrement_stoptheworld_countdown(struct _stoptheworld_state *stw) | ||||||
|  | { | ||||||
|  |     assert(stw->thread_countdown > 0); | ||||||
|  |     if (--stw->thread_countdown == 0) { | ||||||
|  |         _PyEvent_Notify(&stw->stop_event); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #ifdef Py_GIL_DISABLED | ||||||
|  | // Interpreter for _Py_FOR_EACH_THREAD(). For global stop-the-world events,
 | ||||||
|  | // we start with the first interpreter and then iterate over all interpreters.
 | ||||||
|  | // For per-interpreter stop-the-world events, we only operate on the one
 | ||||||
|  | // interpreter.
 | ||||||
|  | static PyInterpreterState * | ||||||
|  | interp_for_stop_the_world(struct _stoptheworld_state *stw) | ||||||
|  | { | ||||||
|  |     return (stw->is_global | ||||||
|  |         ? PyInterpreterState_Head() | ||||||
|  |         : _Py_CONTAINER_OF(stw, PyInterpreterState, stoptheworld)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Loops over threads for a stop-the-world event.
 | ||||||
|  | // For global: all threads in all interpreters
 | ||||||
|  | // For per-interpreter: all threads in the interpreter
 | ||||||
|  | #define _Py_FOR_EACH_THREAD(stw, i, t)                                      \ | ||||||
|  |     for (i = interp_for_stop_the_world((stw));                              \ | ||||||
|  |             i != NULL; i = ((stw->is_global) ? i->next : NULL))             \ | ||||||
|  |         for (t = i->threads.head; t; t = t->next) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // Try to transition threads atomically from the "detached" state to the
 | ||||||
|  | // "gc stopped" state. Returns true if all threads are in the "gc stopped"
 | ||||||
|  | static bool | ||||||
|  | park_detached_threads(struct _stoptheworld_state *stw) | ||||||
|  | { | ||||||
|  |     int num_parked = 0; | ||||||
|  |     PyInterpreterState *i; | ||||||
|  |     PyThreadState *t; | ||||||
|  |     _Py_FOR_EACH_THREAD(stw, i, t) { | ||||||
|  |         int state = _Py_atomic_load_int_relaxed(&t->state); | ||||||
|  |         if (state == _Py_THREAD_DETACHED) { | ||||||
|  |             // Atomically transition to "suspended" if in "detached" state.
 | ||||||
|  |             if (_Py_atomic_compare_exchange_int(&t->state, | ||||||
|  |                                                 &state, _Py_THREAD_SUSPENDED)) { | ||||||
|  |                 num_parked++; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         else if (state == _Py_THREAD_ATTACHED && t != stw->requester) { | ||||||
|  |             // TODO: set this per-thread, rather than per-interpreter.
 | ||||||
|  |             _Py_set_eval_breaker_bit(t->interp, _PY_EVAL_PLEASE_STOP_BIT, 1); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     stw->thread_countdown -= num_parked; | ||||||
|  |     assert(stw->thread_countdown >= 0); | ||||||
|  |     return num_parked > 0 && stw->thread_countdown == 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  | stop_the_world(struct _stoptheworld_state *stw) | ||||||
|  | { | ||||||
|  |     _PyRuntimeState *runtime = &_PyRuntime; | ||||||
|  | 
 | ||||||
|  |     PyMutex_Lock(&stw->mutex); | ||||||
|  |     if (stw->is_global) { | ||||||
|  |         _PyRWMutex_Lock(&runtime->stoptheworld_mutex); | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |         _PyRWMutex_RLock(&runtime->stoptheworld_mutex); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     HEAD_LOCK(runtime); | ||||||
|  |     stw->requested = 1; | ||||||
|  |     stw->thread_countdown = 0; | ||||||
|  |     stw->stop_event = (PyEvent){0};  // zero-initialize (unset)
 | ||||||
|  |     stw->requester = _PyThreadState_GET();  // may be NULL
 | ||||||
|  | 
 | ||||||
|  |     PyInterpreterState *i; | ||||||
|  |     PyThreadState *t; | ||||||
|  |     _Py_FOR_EACH_THREAD(stw, i, t) { | ||||||
|  |         if (t != stw->requester) { | ||||||
|  |             // Count all the other threads (we don't wait on ourself).
 | ||||||
|  |             stw->thread_countdown++; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (stw->thread_countdown == 0) { | ||||||
|  |         HEAD_UNLOCK(runtime); | ||||||
|  |         stw->world_stopped = 1; | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for (;;) { | ||||||
|  |         // Switch threads that are detached to the GC stopped state
 | ||||||
|  |         bool stopped_all_threads = park_detached_threads(stw); | ||||||
|  |         HEAD_UNLOCK(runtime); | ||||||
|  | 
 | ||||||
|  |         if (stopped_all_threads) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         _PyTime_t wait_ns = 1000*1000;  // 1ms (arbitrary, may need tuning)
 | ||||||
|  |         if (PyEvent_WaitTimed(&stw->stop_event, wait_ns)) { | ||||||
|  |             assert(stw->thread_countdown == 0); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         HEAD_LOCK(runtime); | ||||||
|  |     } | ||||||
|  |     stw->world_stopped = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  | start_the_world(struct _stoptheworld_state *stw) | ||||||
|  | { | ||||||
|  |     _PyRuntimeState *runtime = &_PyRuntime; | ||||||
|  |     assert(PyMutex_IsLocked(&stw->mutex)); | ||||||
|  | 
 | ||||||
|  |     HEAD_LOCK(runtime); | ||||||
|  |     stw->requested = 0; | ||||||
|  |     stw->world_stopped = 0; | ||||||
|  |     stw->requester = NULL; | ||||||
|  |     // Switch threads back to the detached state.
 | ||||||
|  |     PyInterpreterState *i; | ||||||
|  |     PyThreadState *t; | ||||||
|  |     _Py_FOR_EACH_THREAD(stw, i, t) { | ||||||
|  |         if (t != stw->requester) { | ||||||
|  |             assert(t->state == _Py_THREAD_SUSPENDED); | ||||||
|  |             _Py_atomic_store_int(&t->state, _Py_THREAD_DETACHED); | ||||||
|  |             _PyParkingLot_UnparkAll(&t->state); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     HEAD_UNLOCK(runtime); | ||||||
|  |     if (stw->is_global) { | ||||||
|  |         _PyRWMutex_Unlock(&runtime->stoptheworld_mutex); | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |         _PyRWMutex_RUnlock(&runtime->stoptheworld_mutex); | ||||||
|  |     } | ||||||
|  |     PyMutex_Unlock(&stw->mutex); | ||||||
|  | } | ||||||
|  | #endif  // Py_GIL_DISABLED
 | ||||||
|  | 
 | ||||||
|  | void | ||||||
|  | _PyEval_StopTheWorldAll(_PyRuntimeState *runtime) | ||||||
|  | { | ||||||
|  | #ifdef Py_GIL_DISABLED | ||||||
|  |     stop_the_world(&runtime->stoptheworld); | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void | ||||||
|  | _PyEval_StartTheWorldAll(_PyRuntimeState *runtime) | ||||||
|  | { | ||||||
|  | #ifdef Py_GIL_DISABLED | ||||||
|  |     start_the_world(&runtime->stoptheworld); | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void | ||||||
|  | _PyEval_StopTheWorld(PyInterpreterState *interp) | ||||||
|  | { | ||||||
|  | #ifdef Py_GIL_DISABLED | ||||||
|  |     stop_the_world(&interp->stoptheworld); | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void | ||||||
|  | _PyEval_StartTheWorld(PyInterpreterState *interp) | ||||||
|  | { | ||||||
|  | #ifdef Py_GIL_DISABLED | ||||||
|  |     start_the_world(&interp->stoptheworld); | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  | 
 | ||||||
| //----------
 | //----------
 | ||||||
| // other API
 | // other API
 | ||||||
| //----------
 | //----------
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Sam Gross
						Sam Gross