@@ -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
0 commit comments