@@ -2606,6 +2606,240 @@ create_managed_weakref_nogc_type(PyObject *self, PyObject *Py_UNUSED(args))
26062606 return PyType_FromSpec (& ManagedWeakrefNoGC_spec );
26072607}
26082608
2609+ static void
2610+ test_interp_guards_common (void )
2611+ {
2612+ PyInterpreterGuard * guard = PyInterpreterGuard_FromCurrent ();
2613+ assert (guard != NULL );
2614+
2615+ PyInterpreterGuard * guard_2 = PyInterpreterGuard_FromCurrent ();
2616+ assert (guard_2 != NULL );
2617+
2618+ // We can close the guards in any order
2619+ PyInterpreterGuard_Close (guard_2 );
2620+ PyInterpreterGuard_Close (guard );
2621+ }
2622+
2623+ static PyObject *
2624+ test_interpreter_guards (PyObject * self , PyObject * unused )
2625+ {
2626+ // Test the main interpreter
2627+ test_interp_guards_common ();
2628+
2629+ // Test a (legacy) subinterpreter
2630+ PyThreadState * save_tstate = PyThreadState_Swap (NULL );
2631+ PyThreadState * interp_tstate = Py_NewInterpreter ();
2632+ test_interp_guards_common ();
2633+ Py_EndInterpreter (interp_tstate );
2634+
2635+ // Test an isolated subinterpreter
2636+ PyInterpreterConfig config = {
2637+ .gil = PyInterpreterConfig_OWN_GIL ,
2638+ .check_multi_interp_extensions = 1
2639+ };
2640+
2641+ PyThreadState * isolated_interp_tstate ;
2642+ PyStatus status = Py_NewInterpreterFromConfig (& isolated_interp_tstate , & config );
2643+ if (PyStatus_Exception (status )) {
2644+ PyErr_SetString (PyExc_RuntimeError , "interpreter creation failed" );
2645+ return NULL ;
2646+ }
2647+
2648+ test_interp_guards_common ();
2649+ Py_EndInterpreter (isolated_interp_tstate );
2650+ PyThreadState_Swap (save_tstate );
2651+ Py_RETURN_NONE ;
2652+ }
2653+
2654+ static PyObject *
2655+ test_thread_state_ensure_nested (PyObject * self , PyObject * unused )
2656+ {
2657+ PyInterpreterGuard * guard = PyInterpreterGuard_FromCurrent ();
2658+ if (guard == NULL ) {
2659+ return NULL ;
2660+ }
2661+ PyThreadState * save_tstate = PyThreadState_Swap (NULL );
2662+ assert (PyGILState_GetThisThreadState () == save_tstate );
2663+ PyThreadState * thread_states [10 ];
2664+
2665+ for (int i = 0 ; i < 10 ; ++ i ) {
2666+ // Test reactivation of the detached tstate.
2667+ thread_states [i ] = PyThreadState_Ensure (guard );
2668+ if (thread_states [i ] == 0 ) {
2669+ PyInterpreterGuard_Close (guard );
2670+ return PyErr_NoMemory ();
2671+ }
2672+
2673+ // No new thread state should've been created.
2674+ assert (PyThreadState_Get () == save_tstate );
2675+ PyThreadState_Release (thread_states [i ]);
2676+ }
2677+
2678+ assert (PyThreadState_GetUnchecked () == NULL );
2679+
2680+ // Similarly, test ensuring with deep nesting and *then* releasing.
2681+ // If the (detached) gilstate matches the interpreter, then it shouldn't
2682+ // create a new thread state.
2683+ for (int i = 0 ; i < 10 ; ++ i ) {
2684+ thread_states [i ] = PyThreadState_Ensure (guard );
2685+ if (thread_states [i ] == 0 ) {
2686+ // This will technically leak other thread states, but it doesn't
2687+ // matter because this is a test.
2688+ PyInterpreterGuard_Close (guard );
2689+ return PyErr_NoMemory ();
2690+ }
2691+
2692+ assert (PyThreadState_Get () == save_tstate );
2693+ }
2694+
2695+ for (int i = 0 ; i < 10 ; ++ i ) {
2696+ assert (PyThreadState_Get () == save_tstate );
2697+ PyThreadState_Release (thread_states [i ]);
2698+ }
2699+
2700+ assert (PyThreadState_GetUnchecked () == NULL );
2701+ PyInterpreterGuard_Close (guard );
2702+ PyThreadState_Swap (save_tstate );
2703+ Py_RETURN_NONE ;
2704+ }
2705+
2706+ static PyObject *
2707+ test_thread_state_ensure_crossinterp (PyObject * self , PyObject * unused )
2708+ {
2709+ PyInterpreterGuard * guard = PyInterpreterGuard_FromCurrent ();
2710+ PyThreadState * save_tstate = PyThreadState_Swap (NULL );
2711+ PyThreadState * interp_tstate = Py_NewInterpreter ();
2712+ assert (interp_tstate != NULL );
2713+
2714+ /* This should create a new thread state for the calling interpreter, *not*
2715+ reactivate the old one. In a real-world scenario, this would arise in
2716+ something like this:
2717+
2718+ def some_func():
2719+ import something
2720+ # This re-enters the main interpreter, but we
2721+ # shouldn't have access to prior thread-locals.
2722+ something.call_something()
2723+
2724+ interp = interpreters.create()
2725+ interp.exec(some_func)
2726+ */
2727+ PyThreadState * thread_state = PyThreadState_Ensure (guard );
2728+ assert (thread_state != NULL );
2729+
2730+ PyThreadState * ensured_tstate = PyThreadState_Get ();
2731+ assert (ensured_tstate != save_tstate );
2732+ assert (PyGILState_GetThisThreadState () == ensured_tstate );
2733+
2734+ // Now though, we should reactivate the thread state
2735+ PyThreadState * other_thread_state = PyThreadState_Ensure (guard );
2736+ assert (other_thread_state != NULL );
2737+ assert (PyThreadState_Get () == ensured_tstate );
2738+
2739+ PyThreadState_Release (other_thread_state );
2740+
2741+ // Ensure that we're restoring the prior thread state
2742+ PyThreadState_Release (thread_state );
2743+ assert (PyThreadState_Get () == interp_tstate );
2744+ assert (PyGILState_GetThisThreadState () == interp_tstate );
2745+
2746+ PyThreadState_Swap (interp_tstate );
2747+ Py_EndInterpreter (interp_tstate );
2748+
2749+ PyInterpreterGuard_Close (guard );
2750+ PyThreadState_Swap (save_tstate );
2751+ Py_RETURN_NONE ;
2752+ }
2753+
2754+ static PyObject *
2755+ test_interp_view_after_shutdown (PyObject * self , PyObject * unused )
2756+ {
2757+ PyThreadState * save_tstate = PyThreadState_Swap (NULL );
2758+ PyThreadState * interp_tstate = Py_NewInterpreter ();
2759+ if (interp_tstate == NULL ) {
2760+ PyThreadState_Swap (save_tstate );
2761+ return PyErr_NoMemory ();
2762+ }
2763+
2764+ PyInterpreterView * view = PyInterpreterView_FromCurrent ();
2765+ if (view == NULL ) {
2766+ Py_EndInterpreter (interp_tstate );
2767+ PyThreadState_Swap (save_tstate );
2768+ return PyErr_NoMemory ();
2769+ }
2770+
2771+ // As a sanity check, ensure that the view actually works
2772+ PyInterpreterGuard * guard = PyInterpreterGuard_FromView (view );
2773+ PyInterpreterGuard_Close (guard );
2774+
2775+ // Now, destroy the interpreter and try to acquire a lock from a view.
2776+ // It should fail.
2777+ Py_EndInterpreter (interp_tstate );
2778+ guard = PyInterpreterGuard_FromView (view );
2779+ assert (guard == NULL );
2780+
2781+ PyThreadState_Swap (save_tstate );
2782+ Py_RETURN_NONE ;
2783+ }
2784+
2785+ static PyObject *
2786+ test_thread_state_ensure_view (PyObject * self , PyObject * unused )
2787+ {
2788+ // For simplicity's sake, we assume that functions won't fail due to being
2789+ // out of memory.
2790+ PyThreadState * save_tstate = PyThreadState_Swap (NULL );
2791+ PyThreadState * interp_tstate = Py_NewInterpreter ();
2792+ assert (interp_tstate != NULL );
2793+ assert (PyInterpreterState_Get () == PyThreadState_GetInterpreter (interp_tstate ));
2794+
2795+ PyInterpreterView * main_view = PyInterpreterView_FromMain ();
2796+ assert (main_view != NULL );
2797+
2798+ PyInterpreterView * view = PyInterpreterView_FromCurrent ();
2799+ assert (view != NULL );
2800+
2801+ Py_BEGIN_ALLOW_THREADS ;
2802+ PyThreadState * tstate = PyThreadState_EnsureFromView (view );
2803+ assert (tstate != NULL );
2804+ assert (PyThreadState_Get () == interp_tstate );
2805+
2806+ // Test a nested call
2807+ PyThreadState * tstate2 = PyThreadState_EnsureFromView (view );
2808+ assert (PyThreadState_Get () == interp_tstate );
2809+
2810+ // We're in a new interpreter now. PyThreadState_EnsureFromView() should
2811+ // now create a new thread state.
2812+ PyThreadState * main_tstate = PyThreadState_EnsureFromView (main_view );
2813+ assert (main_tstate == interp_tstate ); // The old thread state
2814+ assert (PyInterpreterState_Get () == PyInterpreterState_Main ());
2815+
2816+ // Going back to the old interpreter should create a new thread state again.
2817+ PyThreadState * tstate3 = PyThreadState_EnsureFromView (view );
2818+ assert (PyInterpreterState_Get () == PyThreadState_GetInterpreter (interp_tstate ));
2819+ assert (PyThreadState_Get () != interp_tstate );
2820+ PyThreadState_Release (tstate3 );
2821+ PyThreadState_Release (main_tstate );
2822+
2823+ // We're back in the original interpreter. PyThreadState_EnsureFromView() should
2824+ // no longer create a new thread state.
2825+ assert (PyThreadState_Get () == interp_tstate );
2826+ PyThreadState * tstate4 = PyThreadState_EnsureFromView (view );
2827+ assert (PyThreadState_Get () == interp_tstate );
2828+ PyThreadState_Release (tstate4 );
2829+ PyThreadState_Release (tstate2 );
2830+ PyThreadState_Release (tstate );
2831+ assert (PyThreadState_GetUnchecked () == NULL );
2832+ Py_END_ALLOW_THREADS ;
2833+
2834+ assert (PyThreadState_Get () == interp_tstate );
2835+ PyInterpreterView_Close (view );
2836+ PyInterpreterView_Close (main_view );
2837+ Py_EndInterpreter (interp_tstate );
2838+ PyThreadState_Swap (save_tstate );
2839+
2840+ Py_RETURN_NONE ;
2841+ }
2842+
26092843
26102844static PyObject *
26112845test_soft_deprecated_macros (PyObject * Py_UNUSED (self ), PyObject * Py_UNUSED (args ))
@@ -2740,6 +2974,11 @@ static PyMethodDef TestMethods[] = {
27402974 {"create_managed_weakref_nogc_type" ,
27412975 create_managed_weakref_nogc_type , METH_NOARGS },
27422976 {"test_soft_deprecated_macros" , test_soft_deprecated_macros , METH_NOARGS },
2977+ {"test_interpreter_lock" , test_interpreter_guards , METH_NOARGS },
2978+ {"test_thread_state_ensure_nested" , test_thread_state_ensure_nested , METH_NOARGS },
2979+ {"test_thread_state_ensure_crossinterp" , test_thread_state_ensure_crossinterp , METH_NOARGS },
2980+ {"test_interp_view_after_shutdown" , test_interp_view_after_shutdown , METH_NOARGS },
2981+ {"test_thread_state_ensure_view" , test_thread_state_ensure_view , METH_NOARGS },
27432982 {NULL , NULL } /* sentinel */
27442983};
27452984
0 commit comments