Skip to content

Commit 9f7b014

Browse files
committed
Fix issue with re-entrant clearing.
1 parent d75e6d9 commit 9f7b014

2 files changed

Lines changed: 60 additions & 0 deletions

File tree

Programs/_testembed.c

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2878,6 +2878,63 @@ test_concurrent_finalization_stress(void)
28782878

28792879
#undef NUM_THREADS
28802880

2881+
/* A capsule destructor that calls Ensure/Release while the tstate is being
2882+
* cleared by PyThreadState_Release. */
2883+
static void
2884+
capsule_destructor(PyObject *capsule)
2885+
{
2886+
assert(capsule != NULL);
2887+
PyInterpreterGuard *guard = PyCapsule_GetPointer(capsule, "x");
2888+
PyThreadStateToken *token = PyThreadState_Ensure(guard);
2889+
assert(token != NULL);
2890+
PyThreadState_Release(token);
2891+
}
2892+
2893+
static void
2894+
thread_with_tstate_destructor(void *arg)
2895+
{
2896+
PyInterpreterGuard *guard = (PyInterpreterGuard *)arg;
2897+
2898+
/* Triggers fresh tstate path */
2899+
PyThreadStateToken *token = PyThreadState_Ensure(guard);
2900+
assert(token != NULL);
2901+
2902+
/* Stash a capsule whose destructor will run during PyThreadState_Clear. */
2903+
PyObject *capsule = PyCapsule_New(guard, "x", capsule_destructor);
2904+
assert(capsule != NULL);
2905+
2906+
/* We need to put it somewhere it gets cleaned up at PyThreadState_Clear.
2907+
* tstate->dict is cleared during PyThreadState_Clear. */
2908+
PyObject *dict = PyThreadState_GetDict();
2909+
assert(dict != NULL);
2910+
int res = PyDict_SetItemString(dict, "key", capsule);
2911+
assert(res == 0);
2912+
Py_DECREF(capsule);
2913+
2914+
PyThreadState_Release(token);
2915+
PyInterpreterGuard_Close(guard);
2916+
}
2917+
2918+
static int
2919+
test_thread_state_release_with_destructor(void)
2920+
{
2921+
_testembed_initialize();
2922+
PyInterpreterGuard *guard = PyInterpreterGuard_FromCurrent();
2923+
assert(guard != NULL);
2924+
2925+
PyThread_handle_t handle;
2926+
PyThread_ident_t ident;
2927+
if (PyThread_start_joinable_thread(thread_with_tstate_destructor,
2928+
guard, &ident, &handle) < 0) {
2929+
PyInterpreterGuard_Close(guard);
2930+
return -1;
2931+
}
2932+
2933+
PyThread_join_thread(handle);
2934+
Py_Finalize();
2935+
return 0;
2936+
}
2937+
28812938
/* *********************************************************
28822939
* List of test cases and the function that implements it.
28832940
*
@@ -2976,6 +3033,7 @@ static struct TestCase TestCases[] = {
29763033
{"test_main_interpreter_view", test_main_interpreter_view},
29773034
{"test_thread_state_ensure_from_view", test_thread_state_ensure_from_view},
29783035
{"test_concurrent_finalization_stress", test_concurrent_finalization_stress},
3036+
{"test_thread_state_release_with_destructor", test_thread_state_release_with_destructor},
29793037
{NULL, NULL}
29803038
};
29813039

Python/pystate.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3567,7 +3567,9 @@ PyThreadState_Release(PyThreadStateToken *token)
35673567
PyInterpreterGuard *owned_guard = tstate->ensure.owned_guard;
35683568
assert(tstate->ensure.delete_on_release == 1 || tstate->ensure.delete_on_release == 0);
35693569
if (tstate->ensure.delete_on_release) {
3570+
++tstate->ensure.counter;
35703571
PyThreadState_Clear(tstate);
3572+
--tstate->ensure.counter;
35713573
}
35723574

35733575
PyThreadState *check_tstate = PyThreadState_Swap(to_restore);

0 commit comments

Comments
 (0)