Skip to content

Commit af6f6fd

Browse files
committed
2 parents 96efc81 + 40e7e68 commit af6f6fd

8 files changed

Lines changed: 409 additions & 138 deletions

File tree

Include/cpython/pystate.h

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -209,11 +209,19 @@ struct _ts {
209209
PyObject *threading_local_sentinel;
210210
_PyRemoteDebuggerSupport remote_debugger_support;
211211

212-
/* Number of nested PyThreadState_Ensure() calls on this thread state */
213-
Py_ssize_t ensure_counter;
212+
struct {
213+
/* Number of nested PyThreadState_Ensure() calls on this thread state */
214+
Py_ssize_t counter;
215+
216+
/* Thread state that was active before PyThreadState_Ensure() was called. */
217+
PyThreadState *prior_tstate;
218+
219+
/* Should this thread state be deleted upon calling
220+
PyThreadState_Release() (with the counter at 1)?
214221
215-
/* Thread state that was active before PyThreadState_Ensure() was called. */
216-
PyThreadState *prior_ensure;
222+
This is only true for thread states created by PyThreadState_Ensure() */
223+
int delete_on_release;
224+
} ensure;
217225
};
218226

219227
/* other API */
@@ -272,32 +280,36 @@ PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc(
272280

273281
typedef uintptr_t PyInterpreterRef;
274282

275-
PyAPI_FUNC(PyInterpreterRef) PyInterpreterRef_Get(void);
283+
PyAPI_FUNC(int) PyInterpreterRef_Get(PyInterpreterRef *ref);
276284
PyAPI_FUNC(PyInterpreterRef) PyInterpreterRef_Dup(PyInterpreterRef ref);
277-
PyAPI_FUNC(int) PyInterpreterState_AsStrong(PyInterpreterState *interp, PyInterpreterRef *strong_ptr);
285+
PyAPI_FUNC(int) PyInterpreterRef_Main(PyInterpreterRef *ref);
278286
PyAPI_FUNC(void) PyInterpreterRef_Close(PyInterpreterRef ref);
279287
PyAPI_FUNC(PyInterpreterState *) PyInterpreterRef_AsInterpreter(PyInterpreterRef ref);
280288

281289
#define PyInterpreterRef_Close(ref) do { \
282290
PyInterpreterRef_Close(ref); \
283291
ref = 0; \
284-
} while (0); \
292+
} while (0)
285293

286294
/* Weak interpreter references */
287295

288-
typedef struct _interpreter_weakref {
296+
typedef struct _PyInterpreterWeakRef {
289297
int64_t id;
290298
Py_ssize_t refcount;
291-
} PyInterpreterWeakRef;
299+
} _PyInterpreterWeakRef;
292300

293-
PyAPI_FUNC(PyInterpreterWeakRef) PyInterpreterWeakRef_Get(void);
301+
typedef _PyInterpreterWeakRef *PyInterpreterWeakRef;
302+
303+
PyAPI_FUNC(int) PyInterpreterWeakRef_Get(PyInterpreterWeakRef *ptr);
294304
PyAPI_FUNC(PyInterpreterWeakRef) PyInterpreterWeakRef_Dup(PyInterpreterWeakRef wref);
295305
PyAPI_FUNC(int) PyInterpreterWeakRef_AsStrong(PyInterpreterWeakRef wref, PyInterpreterRef *strong_ptr);
296306
PyAPI_FUNC(void) PyInterpreterWeakRef_Close(PyInterpreterWeakRef wref);
297307

298-
// Exports for '_testcapi' shared extension
299-
PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_Refcount(PyInterpreterState *interp);
300-
PyAPI_FUNC(void) _PyInterpreterState_Incref(PyInterpreterState *interp);
308+
#define PyInterpreterWeakRef_Close(ref) do { \
309+
PyInterpreterWeakRef_Close(ref); \
310+
ref = 0; \
311+
} while (0)
312+
301313

302314
PyAPI_FUNC(int) PyThreadState_Ensure(PyInterpreterRef interp_ref);
303315

Include/internal/pycore_pystate.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,10 @@ _Py_RecursionLimit_GetMargin(PyThreadState *tstate)
328328
return Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, here_addr - (intptr_t)_tstate->c_stack_soft_limit, PYOS_STACK_MARGIN_SHIFT);
329329
}
330330

331+
// Exports for '_testinternalcapi' shared extension
332+
PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_Refcount(PyInterpreterState *interp);
333+
PyAPI_FUNC(int) _PyInterpreterState_Incref(PyInterpreterState *interp);
334+
331335
#ifdef __cplusplus
332336
}
333337
#endif

Lib/test/test_embed.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1914,14 +1914,15 @@ def test_audit_run_stdin(self):
19141914
def test_get_incomplete_frame(self):
19151915
self.run_embedded_interpreter("test_get_incomplete_frame")
19161916

1917-
19181917
def test_gilstate_after_finalization(self):
19191918
self.run_embedded_interpreter("test_gilstate_after_finalization")
19201919

1921-
19221920
def test_thread_state_ensure(self):
19231921
self.run_embedded_interpreter("test_thread_state_ensure")
19241922

1923+
def test_main_interpreter_ref(self):
1924+
self.run_embedded_interpreter("test_main_interpreter_ref")
1925+
19251926

19261927
class MiscTests(EmbeddingTestsMixin, unittest.TestCase):
19271928
def test_unicode_id_init(self):

Modules/_testcapimodule.c

Lines changed: 161 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2546,82 +2546,189 @@ toggle_reftrace_printer(PyObject *ob, PyObject *arg)
25462546
Py_RETURN_NONE;
25472547
}
25482548

2549-
static PyObject *
2550-
test_interp_refcount(PyObject *self, PyObject *unused)
2549+
static PyInterpreterRef
2550+
get_strong_ref(void)
2551+
{
2552+
PyInterpreterRef ref;
2553+
if (PyInterpreterRef_Get(&ref) < 0) {
2554+
Py_FatalError("strong reference should not have failed");
2555+
}
2556+
return ref;
2557+
}
2558+
2559+
static void
2560+
test_interp_ref_common(void)
25512561
{
25522562
PyInterpreterState *interp = PyInterpreterState_Get();
2553-
PyInterpreterRef ref1;
2554-
PyInterpreterRef ref2;
2555-
2556-
// Reference counts are technically 0 by default
2557-
assert(_PyInterpreterState_Refcount(interp) == 0);
2558-
ref1 = PyInterpreterRef_Get();
2559-
assert(_PyInterpreterState_Refcount(interp) == 1);
2560-
ref2 = PyInterpreterRef_Get();
2561-
assert(_PyInterpreterState_Refcount(interp) == 2);
2562-
PyInterpreterRef_Close(ref1);
2563-
assert(_PyInterpreterState_Refcount(interp) == 1);
2564-
PyInterpreterRef_Close(ref2);
2565-
assert(_PyInterpreterState_Refcount(interp) == 0);
2566-
2567-
ref1 = PyInterpreterRef_Get();
2568-
ref2 = PyInterpreterRef_Dup(ref1);
2569-
assert(_PyInterpreterState_Refcount(interp) == 2);
2570-
assert(PyInterpreterRef_AsInterpreter(ref1) == interp);
2571-
assert(PyInterpreterRef_AsInterpreter(ref2) == interp);
2572-
PyInterpreterRef_Close(ref1);
2573-
PyInterpreterRef_Close(ref2);
2574-
assert(_PyInterpreterState_Refcount(interp) == 0);
2563+
PyInterpreterRef ref = get_strong_ref();
2564+
assert(PyInterpreterRef_AsInterpreter(ref) == interp);
25752565

2576-
Py_RETURN_NONE;
2566+
PyInterpreterRef ref_2 = PyInterpreterRef_Dup(ref);
2567+
assert(PyInterpreterRef_AsInterpreter(ref_2) == interp);
2568+
2569+
// We can close the references in any order
2570+
PyInterpreterRef_Close(ref);
2571+
PyInterpreterRef_Close(ref_2);
25772572
}
25782573

25792574
static PyObject *
2580-
test_interp_weak_ref(PyObject *self, PyObject *unused)
2575+
test_interpreter_refs(PyObject *self, PyObject *unused)
25812576
{
2582-
PyInterpreterState *interp = PyInterpreterState_Get();
2583-
PyInterpreterWeakRef wref = PyInterpreterWeakRef_Get();
2584-
assert(_PyInterpreterState_Refcount(interp) == 0);
2577+
// Test the main interpreter
2578+
test_interp_ref_common();
25852579

2586-
PyInterpreterRef ref;
2587-
int res = PyInterpreterWeakRef_AsStrong(wref, &ref);
2588-
assert(res == 0);
2589-
assert(PyInterpreterRef_AsInterpreter(ref) == interp);
2590-
assert(_PyInterpreterState_Refcount(interp) == 1);
2591-
PyInterpreterWeakRef_Close(wref);
2592-
PyInterpreterRef_Close(ref);
2580+
// Test a (legacy) subinterpreter
2581+
PyThreadState *save_tstate = PyThreadState_Swap(NULL);
2582+
PyThreadState *interp_tstate = Py_NewInterpreter();
2583+
test_interp_ref_common();
2584+
Py_EndInterpreter(interp_tstate);
2585+
2586+
// Test an isolated subinterpreter
2587+
PyInterpreterConfig config = {
2588+
.gil = PyInterpreterConfig_OWN_GIL,
2589+
.check_multi_interp_extensions = 1
2590+
};
25932591

2592+
PyThreadState *isolated_interp_tstate;
2593+
PyStatus status = Py_NewInterpreterFromConfig(&isolated_interp_tstate, &config);
2594+
if (PyStatus_Exception(status)) {
2595+
PyErr_SetString(PyExc_RuntimeError, "interpreter creation failed");
2596+
return NULL;
2597+
}
2598+
2599+
test_interp_ref_common();
2600+
Py_EndInterpreter(isolated_interp_tstate);
2601+
PyThreadState_Swap(save_tstate);
25942602
Py_RETURN_NONE;
25952603
}
25962604

25972605
static PyObject *
2598-
test_interp_ensure(PyObject *self, PyObject *unused)
2606+
test_thread_state_ensure_nested(PyObject *self, PyObject *unused)
25992607
{
2600-
PyInterpreterState *interp = PyInterpreterState_Get();
2601-
PyInterpreterRef ref = PyInterpreterRef_Get();
2608+
PyInterpreterRef ref = get_strong_ref();
26022609
PyThreadState *save_tstate = PyThreadState_Swap(NULL);
2603-
PyThreadState *tstate = Py_NewInterpreter();
2604-
PyInterpreterRef sub_ref = PyInterpreterRef_Get();
2605-
PyInterpreterState *subinterp = PyThreadState_GetInterpreter(tstate);
2610+
assert(PyGILState_GetThisThreadState() == save_tstate);
26062611

26072612
for (int i = 0; i < 10; ++i) {
2608-
int res = PyThreadState_Ensure(ref);
2609-
assert(res == 0);
2610-
assert(PyInterpreterState_Get() == interp);
2613+
// Test reactivation of the detached tstate.
2614+
if (PyThreadState_Ensure(ref) < 0) {
2615+
PyInterpreterRef_Close(ref);
2616+
return PyErr_NoMemory();
2617+
}
2618+
2619+
// No new thread state should've been created.
2620+
assert(PyThreadState_Get() == save_tstate);
2621+
PyThreadState_Release();
26112622
}
26122623

2624+
assert(PyThreadState_GetUnchecked() == NULL);
2625+
2626+
// Similarly, test ensuring with deep nesting and *then* releasing.
2627+
// If the (detached) gilstate matches the interpreter, then it shouldn't
2628+
// create a new thread state.
26132629
for (int i = 0; i < 10; ++i) {
2614-
int res = PyThreadState_Ensure(sub_ref);
2615-
assert(res == 0);
2616-
assert(PyInterpreterState_Get() == subinterp);
2630+
if (PyThreadState_Ensure(ref) < 0) {
2631+
// This will technically leak other thread states, but it doesn't
2632+
// matter because this is a test.
2633+
PyInterpreterRef_Close(ref);
2634+
return PyErr_NoMemory();
2635+
}
2636+
2637+
assert(PyThreadState_Get() == save_tstate);
26172638
}
26182639

2619-
for (int i = 0; i < 20; ++i) {
2640+
for (int i = 0; i < 10; ++i) {
2641+
assert(PyThreadState_Get() == save_tstate);
26202642
PyThreadState_Release();
26212643
}
26222644

2645+
assert(PyThreadState_GetUnchecked() == NULL);
2646+
PyInterpreterRef_Close(ref);
2647+
PyThreadState_Swap(save_tstate);
2648+
Py_RETURN_NONE;
2649+
}
2650+
2651+
static PyObject *
2652+
test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused)
2653+
{
2654+
PyInterpreterRef ref = get_strong_ref();
2655+
PyThreadState *save_tstate = PyThreadState_Swap(NULL);
2656+
PyThreadState *interp_tstate = Py_NewInterpreter();
2657+
if (interp_tstate == NULL) {
2658+
PyInterpreterRef_Close(ref);
2659+
return PyErr_NoMemory();
2660+
}
2661+
2662+
/* This should create a new thread state for the calling interpreter, *not*
2663+
reactivate the old one. In a real-world scenario, this would arise in
2664+
something like this:
2665+
2666+
def some_func():
2667+
import something
2668+
# This re-enters the main interpreter, but we
2669+
# shouldn't have access to prior thread-locals.
2670+
something.call_something()
2671+
2672+
interp = interpreters.create()
2673+
interp.exec(some_func)
2674+
*/
2675+
if (PyThreadState_Ensure(ref) < 0) {
2676+
PyInterpreterRef_Close(ref);
2677+
return PyErr_NoMemory();
2678+
}
2679+
2680+
PyThreadState *ensured_tstate = PyThreadState_Get();
2681+
assert(ensured_tstate != save_tstate);
2682+
assert(PyInterpreterState_Get() == PyInterpreterRef_AsInterpreter(ref));
2683+
assert(PyGILState_GetThisThreadState() == ensured_tstate);
2684+
2685+
// Now though, we should reactivate the thread state
2686+
if (PyThreadState_Ensure(ref) < 0) {
2687+
PyInterpreterRef_Close(ref);
2688+
return PyErr_NoMemory();
2689+
}
2690+
2691+
assert(PyThreadState_Get() == ensured_tstate);
2692+
PyThreadState_Release();
2693+
2694+
// Ensure that we're restoring the prior thread state
2695+
PyThreadState_Release();
2696+
assert(PyThreadState_Get() == interp_tstate);
2697+
assert(PyGILState_GetThisThreadState() == interp_tstate);
2698+
2699+
PyThreadState_Swap(interp_tstate);
2700+
Py_EndInterpreter(interp_tstate);
2701+
26232702
PyInterpreterRef_Close(ref);
2624-
PyInterpreterRef_Close(sub_ref);
2703+
PyThreadState_Swap(save_tstate);
2704+
Py_RETURN_NONE;
2705+
}
2706+
2707+
static PyObject *
2708+
test_weak_interpreter_ref_after_shutdown(PyObject *self, PyObject *unused)
2709+
{
2710+
PyThreadState *save_tstate = PyThreadState_Swap(NULL);
2711+
PyInterpreterWeakRef wref;
2712+
PyThreadState *interp_tstate = Py_NewInterpreter();
2713+
if (interp_tstate == NULL) {
2714+
return PyErr_NoMemory();
2715+
}
2716+
2717+
int res = PyInterpreterWeakRef_Get(&wref);
2718+
(void)res;
2719+
assert(res == 0);
2720+
2721+
// As a sanity check, ensure that the weakref actually works
2722+
PyInterpreterRef ref;
2723+
res = PyInterpreterWeakRef_AsStrong(wref, &ref);
2724+
assert(res == 0);
2725+
PyInterpreterRef_Close(ref);
2726+
2727+
// Now, destroy the interpreter and try to acquire a weak reference.
2728+
// It should fail.
2729+
Py_EndInterpreter(interp_tstate);
2730+
res = PyInterpreterWeakRef_AsStrong(wref, &ref);
2731+
assert(res == -1);
26252732

26262733
PyThreadState_Swap(save_tstate);
26272734
Py_RETURN_NONE;
@@ -2721,9 +2828,10 @@ static PyMethodDef TestMethods[] = {
27212828
{"test_atexit", test_atexit, METH_NOARGS},
27222829
{"code_offset_to_line", _PyCFunction_CAST(code_offset_to_line), METH_FASTCALL},
27232830
{"toggle_reftrace_printer", toggle_reftrace_printer, METH_O},
2724-
{"test_interp_refcount", test_interp_refcount, METH_NOARGS},
2725-
{"test_interp_weak_ref", test_interp_weak_ref, METH_NOARGS},
2726-
{"test_interp_ensure", test_interp_ensure, METH_NOARGS},
2831+
{"test_interpreter_refs", test_interpreter_refs, METH_NOARGS},
2832+
{"test_thread_state_ensure_nested", test_thread_state_ensure_nested, METH_NOARGS},
2833+
{"test_thread_state_ensure_crossinterp", test_thread_state_ensure_crossinterp, METH_NOARGS},
2834+
{"test_weak_interpreter_ref_after_shutdown", test_weak_interpreter_ref_after_shutdown, METH_NOARGS},
27272835
{NULL, NULL} /* sentinel */
27282836
};
27292837

0 commit comments

Comments
 (0)