Skip to content

Commit 43b0798

Browse files
committed
Copy the reference implementation.
1 parent 29a92ab commit 43b0798

11 files changed

Lines changed: 825 additions & 26 deletions

File tree

Include/cpython/pystate.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ struct _ts {
105105
# define _PyThreadState_WHENCE_INIT 1
106106
# define _PyThreadState_WHENCE_FINI 2
107107
# define _PyThreadState_WHENCE_THREADING 3
108-
# define _PyThreadState_WHENCE_GILSTATE 4
108+
# define _PyThreadState_WHENCE_C_API 4
109109
# define _PyThreadState_WHENCE_EXEC 5
110110
# define _PyThreadState_WHENCE_THREADING_DAEMON 6
111111
#endif
@@ -239,6 +239,20 @@ struct _ts {
239239
// structure and all share the same per-interpreter structure).
240240
PyStats *pystats;
241241
#endif
242+
243+
struct {
244+
/* Number of nested PyThreadState_Ensure() calls on this thread state */
245+
Py_ssize_t counter;
246+
247+
/* Should this thread state be deleted upon calling
248+
PyThreadState_Release() (with the counter at 1)?
249+
250+
This is only true for thread states created by PyThreadState_Ensure() */
251+
int delete_on_release;
252+
253+
/* The interpreter guard owned by PyThreadState_EnsureFromView(), if any. */
254+
PyInterpreterGuard *owned_guard;
255+
} ensure;
242256
};
243257

244258
/* other API */

Include/internal/pycore_interp_structs.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,6 +1053,12 @@ struct _is {
10531053
#endif
10541054
#endif
10551055

1056+
struct {
1057+
_PyRWMutex lock;
1058+
Py_ssize_t countdown;
1059+
PyEvent done;
1060+
} finalization_guards;
1061+
10561062
/* the initial PyInterpreterState.threads.head */
10571063
_PyThreadStateImpl _initial_thread;
10581064
// _initial_thread should be the last field of PyInterpreterState.

Include/internal/pycore_pystate.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,20 @@ _Py_RecursionLimit_GetMargin(PyThreadState *tstate)
338338
#endif
339339
}
340340

341+
/* PEP 788 structures. */
342+
343+
struct _PyInterpreterGuard {
344+
PyInterpreterState *interp;
345+
};
346+
347+
struct _PyInterpreterView {
348+
int64_t id;
349+
};
350+
351+
// Exports for '_testinternalcapi' shared extension
352+
PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_GuardCountdown(PyInterpreterState *interp);
353+
PyAPI_FUNC(PyInterpreterState *) _PyInterpreterGuard_GetInterpreter(PyInterpreterGuard *guard);
354+
341355
#ifdef __cplusplus
342356
}
343357
#endif

Include/pystate.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,23 @@ PyAPI_FUNC(void) PyGILState_Release(PyGILState_STATE);
120120
PyAPI_FUNC(PyThreadState *) PyGILState_GetThisThreadState(void);
121121

122122

123+
/* PEP 788 -- Interpreter guards and views. */
124+
125+
typedef struct _PyInterpreterGuard PyInterpreterGuard;
126+
typedef struct _PyInterpreterView PyInterpreterView;
127+
128+
PyAPI_FUNC(PyInterpreterGuard *) PyInterpreterGuard_FromCurrent(void);
129+
PyAPI_FUNC(void) PyInterpreterGuard_Close(PyInterpreterGuard *guard);
130+
PyAPI_FUNC(PyInterpreterGuard *) PyInterpreterGuard_FromView(PyInterpreterView *view);
131+
132+
PyAPI_FUNC(PyInterpreterView *) PyInterpreterView_FromCurrent(void);
133+
PyAPI_FUNC(void) PyInterpreterView_Close(PyInterpreterView *view);
134+
PyAPI_FUNC(PyInterpreterView *) PyInterpreterView_FromMain(void);
135+
136+
PyAPI_FUNC(PyThreadState *) PyThreadState_Ensure(PyInterpreterGuard *guard);
137+
PyAPI_FUNC(PyThreadState *) PyThreadState_EnsureFromView(PyInterpreterView *view);
138+
PyAPI_FUNC(void) PyThreadState_Release(PyThreadState *tstate);
139+
123140
#ifndef Py_LIMITED_API
124141
# define Py_CPYTHON_PYSTATE_H
125142
# include "cpython/pystate.h"

Lib/test/test_embed.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1993,10 +1993,18 @@ def test_audit_run_stdin(self):
19931993
def test_get_incomplete_frame(self):
19941994
self.run_embedded_interpreter("test_get_incomplete_frame")
19951995

1996-
19971996
def test_gilstate_after_finalization(self):
19981997
self.run_embedded_interpreter("test_gilstate_after_finalization")
19991998

1999+
def test_thread_state_ensure(self):
2000+
self.run_embedded_interpreter("test_thread_state_ensure")
2001+
2002+
def test_main_interpreter_view(self):
2003+
self.run_embedded_interpreter("test_main_interpreter_view")
2004+
2005+
def test_thread_state_ensure_from_view(self):
2006+
self.run_embedded_interpreter("test_thread_state_ensure_from_view")
2007+
20002008

20012009
class MiscTests(EmbeddingTestsMixin, unittest.TestCase):
20022010
def test_unicode_id_init(self):

Modules/_testcapimodule.c

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

26102844
static PyObject*
26112845
test_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

Comments
 (0)