@@ -2935,6 +2935,56 @@ test_thread_state_release_with_destructor(void)
29352935 return 0 ;
29362936}
29372937
2938+ static int
2939+ test_thread_state_ensure_from_view_interp_switch (void )
2940+ {
2941+ _testembed_initialize ();
2942+
2943+ /* The main tstate is already attached and was NOT created by
2944+ * PyThreadState_Ensure, so delete_on_release == 0. */
2945+ PyInterpreterState * interp = _PyInterpreterState_GET ();
2946+ assert (interp != NULL );
2947+ PyInterpreterView * view = PyInterpreterView_FromCurrent ();
2948+ assert (view != NULL );
2949+
2950+ /* First Ensure/Release pair on this pre-existing tstate. */
2951+ assert (_PyThreadState_GET () != NULL );
2952+ PyThreadStateToken * t1 = PyThreadState_EnsureFromView (view );
2953+ assert (t1 != NULL );
2954+ assert (_PyInterpreterState_GuardCountdown (interp ) == 1 );
2955+ PyThreadState_Release (t1 );
2956+ assert (_PyInterpreterState_GuardCountdown (interp ) == 0 );
2957+ assert (_PyThreadState_GET () != NULL );
2958+
2959+ /* tstate->ensure.owned_guard now points at the freed guard. */
2960+
2961+ /* Re-attach: Bug B detaches us as a side effect (separate repro). */
2962+ PyThreadState * save = PyThreadState_Swap (NULL );
2963+
2964+ PyThreadStateToken * t2 = PyThreadState_EnsureFromView (view );
2965+ assert (_PyInterpreterState_GuardCountdown (interp ) == 1 );
2966+ assert (t2 != NULL );
2967+ PyThreadState_Release (t2 );
2968+ assert (_PyInterpreterState_GuardCountdown (interp ) == 0 );
2969+ assert (_PyThreadState_GET () == NULL );
2970+
2971+ PyThreadState_Swap (save );
2972+
2973+ /* In a release build (no assertion) the second Ensure silently
2974+ * skipped storing its guard and Release decremented the global
2975+ * counter from 0, wrapping it to GUARDS_NOT_ALLOWED. All future
2976+ * guard acquisitions then fail: */
2977+ PyInterpreterGuard * g = PyInterpreterGuard_FromCurrent ();
2978+ assert (g != NULL );
2979+ assert (_PyInterpreterState_GuardCountdown (interp ) == 1 );
2980+ PyInterpreterGuard_Close (g );
2981+ assert (_PyInterpreterState_GuardCountdown (interp ) == 0 );
2982+
2983+ PyInterpreterView_Close (view );
2984+ Py_Finalize ();
2985+ return 0 ;
2986+ }
2987+
29382988/* *********************************************************
29392989 * List of test cases and the function that implements it.
29402990 *
@@ -3034,6 +3084,7 @@ static struct TestCase TestCases[] = {
30343084 {"test_thread_state_ensure_from_view" , test_thread_state_ensure_from_view },
30353085 {"test_concurrent_finalization_stress" , test_concurrent_finalization_stress },
30363086 {"test_thread_state_release_with_destructor" , test_thread_state_release_with_destructor },
3087+ {"test_thread_state_ensure_from_view_interp_switch" , test_thread_state_ensure_from_view_interp_switch },
30373088 {NULL , NULL }
30383089};
30393090
0 commit comments