Skip to content

Commit 2dda7a4

Browse files
committed
Rely on stop-the-world and the GIL instead of a dedicated RW mutex.
This solution is much simpler, but will perform a little bit worse. Instead of using a dedicated lock on the interpreter state, we can simply use stop-the-world (and the GIL on the default build) to ensure that no other threads can create pre-finalization callbacks concurrently.
1 parent a794188 commit 2dda7a4

File tree

5 files changed

+19
-40
lines changed

5 files changed

+19
-40
lines changed

Include/internal/pycore_interp_structs.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -971,10 +971,6 @@ struct _is {
971971
# endif
972972
#endif
973973

974-
/* The "pre-finalization" lock, which protects against things like starting
975-
* threads. The exclusive writer is only used when the interpreter finalizes. */
976-
_PyRWMutex prefini_mutex;
977-
978974
/* the initial PyInterpreterState.threads.head */
979975
_PyThreadStateImpl _initial_thread;
980976
// _initial_thread should be the last field of PyInterpreterState.

Modules/_threadmodule.c

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -176,13 +176,11 @@ ThreadHandle_get_os_handle(ThreadHandle *handle, PyThread_handle_t *os_handle)
176176
}
177177

178178
static void
179-
add_to_shutdown_handles(thread_module_state *state, ThreadHandle *handle, PyInterpreterState *interp)
179+
add_to_shutdown_handles(thread_module_state *state, ThreadHandle *handle)
180180
{
181-
_PyRWMutex_RLock(&interp->prefini_mutex);
182181
HEAD_LOCK(&_PyRuntime);
183182
llist_insert_tail(&state->shutdown_handles, &handle->shutdown_node);
184183
HEAD_UNLOCK(&_PyRuntime);
185-
_PyRWMutex_RUnlock(&interp->prefini_mutex);
186184
}
187185

188186
static void
@@ -197,16 +195,13 @@ clear_shutdown_handles(thread_module_state *state)
197195
}
198196

199197
static void
200-
remove_from_shutdown_handles(ThreadHandle *handle, PyInterpreterState *interp)
198+
remove_from_shutdown_handles(ThreadHandle *handle)
201199
{
202-
assert(interp != NULL);
203-
_PyRWMutex_RLock(&interp->prefini_mutex);
204200
HEAD_LOCK(&_PyRuntime);
205201
if (handle->shutdown_node.next != NULL) {
206202
llist_remove(&handle->shutdown_node);
207203
}
208204
HEAD_UNLOCK(&_PyRuntime);
209-
_PyRWMutex_RUnlock(&interp->prefini_mutex);
210205
}
211206

212207
static ThreadHandle *
@@ -314,7 +309,7 @@ _PyThread_AfterFork(struct _pythread_runtime_state *state)
314309
handle->mutex = (PyMutex){_Py_UNLOCKED};
315310
_PyEvent_Notify(&handle->thread_is_exiting);
316311
llist_remove(node);
317-
remove_from_shutdown_handles(handle, _PyInterpreterState_GET());
312+
remove_from_shutdown_handles(handle);
318313
}
319314
}
320315

@@ -347,7 +342,6 @@ thread_run(void *boot_raw)
347342
{
348343
struct bootstate *boot = (struct bootstate *) boot_raw;
349344
PyThreadState *tstate = boot->tstate;
350-
PyInterpreterState *interp = tstate->interp;
351345

352346
// Wait until the handle is marked as running
353347
PyEvent_Wait(&boot->handle_ready);
@@ -374,7 +368,7 @@ thread_run(void *boot_raw)
374368

375369
_PyThreadState_Bind(tstate);
376370
PyEval_AcquireThread(tstate);
377-
_Py_atomic_add_ssize(&interp->threads.count, 1);
371+
_Py_atomic_add_ssize(&tstate->interp->threads.count, 1);
378372

379373
PyObject *res = PyObject_Call(boot->func, boot->args, boot->kwargs);
380374
if (res == NULL) {
@@ -392,13 +386,13 @@ thread_run(void *boot_raw)
392386

393387
thread_bootstate_free(boot, 1);
394388

395-
_Py_atomic_add_ssize(&interp->threads.count, -1);
389+
_Py_atomic_add_ssize(&tstate->interp->threads.count, -1);
396390
PyThreadState_Clear(tstate);
397391
_PyThreadState_DeleteCurrent(tstate);
398392

399393
exit:
400394
// Don't need to wait for this thread anymore
401-
remove_from_shutdown_handles(handle, interp);
395+
remove_from_shutdown_handles(handle);
402396

403397
_PyEvent_Notify(&handle->thread_is_exiting);
404398
ThreadHandle_decref(handle);
@@ -1877,12 +1871,12 @@ do_start_new_thread(thread_module_state *state, PyObject *func, PyObject *args,
18771871
// Add the handle before starting the thread to avoid adding a handle
18781872
// to a thread that has already finished (i.e. if the thread finishes
18791873
// before the call to `ThreadHandle_start()` below returns).
1880-
add_to_shutdown_handles(state, handle, interp);
1874+
add_to_shutdown_handles(state, handle);
18811875
}
18821876

18831877
if (ThreadHandle_start(handle, func, args, kwargs) < 0) {
18841878
if (!daemon) {
1885-
remove_from_shutdown_handles(handle, interp);
1879+
remove_from_shutdown_handles(handle);
18861880
}
18871881
return -1;
18881882
}

Modules/atexitmodule.c

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -196,15 +196,9 @@ atexit_register(PyObject *module, PyObject *args, PyObject *kwargs)
196196
return NULL;
197197
}
198198

199-
PyInterpreterState *interp = _PyInterpreterState_GET();
200-
assert(interp != NULL);
201199
struct atexit_state *state = get_atexit_state();
202-
_PyRWMutex_RLock(&interp->prefini_mutex);
203200
// atexit callbacks go in a LIFO order
204-
int res = PyList_Insert(state->callbacks, 0, callback);
205-
_PyRWMutex_RUnlock(&interp->prefini_mutex);
206-
207-
if (res < 0)
201+
if (PyList_Insert(state->callbacks, 0, callback) < 0)
208202
{
209203
Py_DECREF(callback);
210204
return NULL;

Python/ceval_gil.c

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -785,10 +785,8 @@ _PyEval_AddPendingCall(PyInterpreterState *interp,
785785
}
786786

787787
PyMutex_Lock(&pending->mutex);
788-
_PyRWMutex_RLock(&interp->prefini_mutex);
789788
_Py_add_pending_call_result result =
790789
_push_pending_call(pending, func, arg, flags);
791-
_PyRWMutex_RUnlock(&interp->prefini_mutex);
792790
PyMutex_Unlock(&pending->mutex);
793791

794792
if (main_only) {
@@ -919,7 +917,7 @@ clear_pending_handling_thread(struct _pending_calls *pending)
919917
}
920918

921919
static int
922-
make_pending_calls_lock_held(PyThreadState *tstate)
920+
make_pending_calls(PyThreadState *tstate)
923921
{
924922
PyInterpreterState *interp = tstate->interp;
925923
struct _pending_calls *pending = &interp->ceval.pending;
@@ -977,15 +975,6 @@ make_pending_calls_lock_held(PyThreadState *tstate)
977975
return 0;
978976
}
979977

980-
static int
981-
make_pending_calls(PyThreadState *tstate)
982-
{
983-
_PyRWMutex_RLock(&tstate->interp->prefini_mutex);
984-
int res = make_pending_calls_lock_held(tstate);
985-
_PyRWMutex_RUnlock(&tstate->interp->prefini_mutex);
986-
return res;
987-
}
988-
989978

990979
void
991980
_Py_set_eval_breaker_bit_all(PyInterpreterState *interp, uintptr_t bit)

Python/pylifecycle.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2036,6 +2036,8 @@ make_pre_finalization_calls(PyThreadState *tstate)
20362036
* could start a thread or vice versa. To ensure that we properly clean
20372037
* call everything, we run these in a loop until none of them run anything. */
20382038
for (;;) {
2039+
assert(!interp->runtime->stoptheworld.world_stopped);
2040+
20392041
// Wrap up existing "threading"-module-created, non-daemon threads.
20402042
wait_for_thread_shutdown(tstate);
20412043

@@ -2054,15 +2056,18 @@ make_pre_finalization_calls(PyThreadState *tstate)
20542056

20552057
_PyAtExit_Call(tstate->interp);
20562058

2057-
_PyRWMutex_Lock(&tstate->interp->prefini_mutex);
2059+
// XXX Why does _PyThreadState_DeleteList() rely on all interpreters
2060+
// being stopped?
2061+
_PyEval_StopTheWorldAll(interp->runtime);
20582062
int should_continue = (interp_has_threads(interp)
20592063
|| interp_has_atexit_callbacks(interp)
20602064
|| interp_has_pending_calls(interp));
2061-
_PyRWMutex_Unlock(&tstate->interp->prefini_mutex);
20622065
if (!should_continue) {
20632066
break;
20642067
}
2068+
_PyEval_StartTheWorldAll(interp->runtime);
20652069
}
2070+
assert(interp->runtime->stoptheworld.world_stopped);
20662071
}
20672072

20682073
static int
@@ -2099,7 +2104,7 @@ _Py_Finalize(_PyRuntimeState *runtime)
20992104
#endif
21002105

21012106
/* Ensure that remaining threads are detached */
2102-
_PyEval_StopTheWorldAll(runtime);
2107+
assert(tstate->interp->runtime->stoptheworld.world_stopped);
21032108

21042109
/* Remaining daemon threads will be trapped in PyThread_hang_thread
21052110
when they attempt to take the GIL (ex: PyEval_RestoreThread()). */
@@ -2490,6 +2495,7 @@ Py_EndInterpreter(PyThreadState *tstate)
24902495
/* Remaining daemon threads will automatically exit
24912496
when they attempt to take the GIL (ex: PyEval_RestoreThread()). */
24922497
_PyInterpreterState_SetFinalizing(interp, tstate);
2498+
_PyEval_StartTheWorldAll(interp->runtime);
24932499

24942500
// XXX Call something like _PyImport_Disable() here?
24952501

0 commit comments

Comments
 (0)