Skip to content

Commit cf81d43

Browse files
Merge branch 'main' into isolate-io/winconsoleio
2 parents 7c9065f + 92d8bff commit cf81d43

File tree

11 files changed

+212
-47
lines changed

11 files changed

+212
-47
lines changed

Include/internal/pycore_ceval.h

+2
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,9 @@ extern int _PyEval_ThreadsInitialized(void);
100100
extern PyStatus _PyEval_InitGIL(PyThreadState *tstate, int own_gil);
101101
extern void _PyEval_FiniGIL(PyInterpreterState *interp);
102102

103+
extern void _PyEval_AcquireLock(PyThreadState *tstate);
103104
extern void _PyEval_ReleaseLock(PyThreadState *tstate);
105+
extern PyThreadState * _PyThreadState_SwapNoGIL(PyThreadState *);
104106

105107
extern void _PyEval_DeactivateOpCache(void);
106108

Lib/test/test_import/__init__.py

+20
Original file line numberDiff line numberDiff line change
@@ -1861,6 +1861,26 @@ def test_multi_init_extension_non_isolated_compat(self):
18611861
with self.subTest(f'{modname}: not strict'):
18621862
self.check_compatible_here(modname, filename, strict=False)
18631863

1864+
@unittest.skipIf(_testmultiphase is None, "test requires _testmultiphase module")
1865+
def test_multi_init_extension_per_interpreter_gil_compat(self):
1866+
modname = '_test_shared_gil_only'
1867+
filename = _testmultiphase.__file__
1868+
loader = ExtensionFileLoader(modname, filename)
1869+
spec = importlib.util.spec_from_loader(modname, loader)
1870+
module = importlib.util.module_from_spec(spec)
1871+
loader.exec_module(module)
1872+
sys.modules[modname] = module
1873+
1874+
require_extension(module)
1875+
with self.subTest(f'{modname}: isolated, strict'):
1876+
self.check_incompatible_here(modname, filename, isolated=True)
1877+
with self.subTest(f'{modname}: not isolated, strict'):
1878+
self.check_compatible_here(modname, filename,
1879+
strict=True, isolated=False)
1880+
with self.subTest(f'{modname}: not isolated, not strict'):
1881+
self.check_compatible_here(modname, filename,
1882+
strict=False, isolated=False)
1883+
18641884
def test_python_compat(self):
18651885
module = 'threading'
18661886
require_pure_python(module)

Lib/test/test_importlib/extension/test_loader.py

+2
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,8 @@ def test_bad_modules(self):
348348
'exec_err',
349349
'exec_raise',
350350
'exec_unreported_exception',
351+
'multiple_create_slots',
352+
'multiple_multiple_interpreters_slots',
351353
]:
352354
with self.subTest(name_base):
353355
name = self.name + '_' + name_base
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Multi-phase init extension modules may now indicate that they support
2+
running in subinterpreters that have their own GIL. This is done by using
3+
``Py_MOD_PER_INTERPRETER_GIL_SUPPORTED`` as the value for the
4+
``Py_mod_multiple_interpreters`` module def slot. Otherwise the module, by
5+
default, cannot be imported in such subinterpreters. (This does not affect
6+
the main interpreter or subinterpreters that do not have their own GIL.) In
7+
addition to the isolation that multi-phase init already normally requires,
8+
support for per-interpreter GIL involves one additional constraint:
9+
thread-safety. If the module has external (linked) dependencies and those
10+
libraries have any state that isn't thread-safe then the module must do the
11+
additional work to add thread-safety. This should be an uncommon case.

Modules/_io/_iomodule.c

-3
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,6 @@ iomodule_traverse(PyObject *mod, visitproc visit, void *arg) {
580580
_PyIO_State *state = get_io_state(mod);
581581
if (!state->initialized)
582582
return 0;
583-
Py_VISIT(state->locale_module);
584583
Py_VISIT(state->unsupported_operation);
585584

586585
Py_VISIT(state->PyIncrementalNewlineDecoder_Type);
@@ -608,8 +607,6 @@ iomodule_clear(PyObject *mod) {
608607
_PyIO_State *state = get_io_state(mod);
609608
if (!state->initialized)
610609
return 0;
611-
if (state->locale_module != NULL)
612-
Py_CLEAR(state->locale_module);
613610
Py_CLEAR(state->unsupported_operation);
614611

615612
Py_CLEAR(state->PyIncrementalNewlineDecoder_Type);

Modules/_io/_iomodule.h

-2
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,6 @@ extern PyModuleDef _PyIO_Module;
143143

144144
typedef struct {
145145
int initialized;
146-
PyObject *locale_module;
147-
148146
PyObject *unsupported_operation;
149147

150148
/* Types */

Modules/_testmultiphase.c

+59-1
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,27 @@ PyInit__testmultiphase_export_unreported_exception(void)
681681
return PyModuleDef_Init(&main_def);
682682
}
683683

684+
static PyObject*
685+
createfunc_noop(PyObject *spec, PyModuleDef *def)
686+
{
687+
return PyModule_New("spam");
688+
}
689+
690+
static PyModuleDef_Slot slots_multiple_create_slots[] = {
691+
{Py_mod_create, createfunc_noop},
692+
{Py_mod_create, createfunc_noop},
693+
{0, NULL},
694+
};
695+
696+
static PyModuleDef def_multiple_create_slots = TEST_MODULE_DEF(
697+
"_testmultiphase_multiple_create_slots", slots_multiple_create_slots, NULL);
698+
699+
PyMODINIT_FUNC
700+
PyInit__testmultiphase_multiple_create_slots(void)
701+
{
702+
return PyModuleDef_Init(&def_multiple_create_slots);
703+
}
704+
684705
static PyObject*
685706
createfunc_null(PyObject *spec, PyModuleDef *def)
686707
{
@@ -892,7 +913,24 @@ PyInit__test_module_state_shared(void)
892913
}
893914

894915

895-
/* multiple interpreters supports */
916+
/* multiple interpreters support */
917+
918+
static PyModuleDef_Slot slots_multiple_multiple_interpreters_slots[] = {
919+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
920+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
921+
{0, NULL},
922+
};
923+
924+
static PyModuleDef def_multiple_multiple_interpreters_slots = TEST_MODULE_DEF(
925+
"_testmultiphase_multiple_multiple_interpreters_slots",
926+
slots_multiple_multiple_interpreters_slots,
927+
NULL);
928+
929+
PyMODINIT_FUNC
930+
PyInit__testmultiphase_multiple_multiple_interpreters_slots(void)
931+
{
932+
return PyModuleDef_Init(&def_multiple_multiple_interpreters_slots);
933+
}
896934

897935
static PyModuleDef_Slot non_isolated_slots[] = {
898936
{Py_mod_exec, execfunc},
@@ -909,3 +947,23 @@ PyInit__test_non_isolated(void)
909947
{
910948
return PyModuleDef_Init(&non_isolated_def);
911949
}
950+
951+
952+
static PyModuleDef_Slot shared_gil_only_slots[] = {
953+
{Py_mod_exec, execfunc},
954+
/* Note that Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED is the default.
955+
We put it here explicitly to draw attention to the contrast
956+
with Py_MOD_PER_INTERPRETER_GIL_SUPPORTED. */
957+
{Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED},
958+
{0, NULL},
959+
};
960+
961+
static PyModuleDef shared_gil_only_def = TEST_MODULE_DEF("_test_shared_gil_only",
962+
shared_gil_only_slots,
963+
testexport_methods);
964+
965+
PyMODINIT_FUNC
966+
PyInit__test_shared_gil_only(void)
967+
{
968+
return PyModuleDef_Init(&shared_gil_only_def);
969+
}

Objects/moduleobject.c

+7-1
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,13 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
323323
goto error;
324324
}
325325
}
326-
// XXX Do a similar check once we have PyInterpreterState.ceval.own_gil.
326+
else if (multiple_interpreters != Py_MOD_PER_INTERPRETER_GIL_SUPPORTED
327+
&& interp->ceval.own_gil
328+
&& !_Py_IsMainInterpreter(interp)
329+
&& _PyImport_CheckSubinterpIncompatibleExtensionAllowed(name) < 0)
330+
{
331+
goto error;
332+
}
327333

328334
if (create) {
329335
m = create(spec, def);

Python/ceval_gil.c

+57-27
Original file line numberDiff line numberDiff line change
@@ -499,42 +499,66 @@ PyEval_ThreadsInitialized(void)
499499
return _PyEval_ThreadsInitialized();
500500
}
501501

502+
static inline int
503+
current_thread_holds_gil(struct _gil_runtime_state *gil, PyThreadState *tstate)
504+
{
505+
if (((PyThreadState*)_Py_atomic_load_relaxed(&gil->last_holder)) != tstate) {
506+
return 0;
507+
}
508+
return _Py_atomic_load_relaxed(&gil->locked);
509+
}
510+
511+
static void
512+
init_shared_gil(PyInterpreterState *interp, struct _gil_runtime_state *gil)
513+
{
514+
assert(gil_created(gil));
515+
interp->ceval.gil = gil;
516+
interp->ceval.own_gil = 0;
517+
}
518+
519+
static void
520+
init_own_gil(PyInterpreterState *interp, struct _gil_runtime_state *gil)
521+
{
522+
assert(!gil_created(gil));
523+
create_gil(gil);
524+
assert(gil_created(gil));
525+
interp->ceval.gil = gil;
526+
interp->ceval.own_gil = 1;
527+
}
528+
502529
PyStatus
503530
_PyEval_InitGIL(PyThreadState *tstate, int own_gil)
504531
{
505532
assert(tstate->interp->ceval.gil == NULL);
533+
int locked;
506534
if (!own_gil) {
507535
PyInterpreterState *main_interp = _PyInterpreterState_Main();
508536
assert(tstate->interp != main_interp);
509537
struct _gil_runtime_state *gil = main_interp->ceval.gil;
510-
assert(gil_created(gil));
511-
tstate->interp->ceval.gil = gil;
512-
tstate->interp->ceval.own_gil = 0;
513-
return _PyStatus_OK();
538+
init_shared_gil(tstate->interp, gil);
539+
locked = current_thread_holds_gil(gil, tstate);
514540
}
515-
516541
/* XXX per-interpreter GIL */
517-
struct _gil_runtime_state *gil = &tstate->interp->runtime->ceval.gil;
518-
if (!_Py_IsMainInterpreter(tstate->interp)) {
542+
else if (!_Py_IsMainInterpreter(tstate->interp)) {
519543
/* Currently, the GIL is shared by all interpreters,
520544
and only the main interpreter is responsible to create
521545
and destroy it. */
522-
assert(gil_created(gil));
523-
tstate->interp->ceval.gil = gil;
546+
struct _gil_runtime_state *main_gil = _PyInterpreterState_Main()->ceval.gil;
547+
init_shared_gil(tstate->interp, main_gil);
524548
// XXX For now we lie.
525549
tstate->interp->ceval.own_gil = 1;
526-
return _PyStatus_OK();
550+
locked = current_thread_holds_gil(main_gil, tstate);
551+
}
552+
else {
553+
PyThread_init_thread();
554+
// XXX per-interpreter GIL: switch to interp->_gil.
555+
init_own_gil(tstate->interp, &tstate->interp->runtime->ceval.gil);
556+
locked = 0;
557+
}
558+
if (!locked) {
559+
take_gil(tstate);
527560
}
528-
assert(own_gil);
529-
530-
assert(!gil_created(gil));
531561

532-
PyThread_init_thread();
533-
create_gil(gil);
534-
assert(gil_created(gil));
535-
tstate->interp->ceval.gil = gil;
536-
tstate->interp->ceval.own_gil = 1;
537-
take_gil(tstate);
538562
return _PyStatus_OK();
539563
}
540564

@@ -611,9 +635,17 @@ PyEval_ReleaseLock(void)
611635
drop_gil(ceval, tstate);
612636
}
613637

638+
void
639+
_PyEval_AcquireLock(PyThreadState *tstate)
640+
{
641+
_Py_EnsureTstateNotNULL(tstate);
642+
take_gil(tstate);
643+
}
644+
614645
void
615646
_PyEval_ReleaseLock(PyThreadState *tstate)
616647
{
648+
_Py_EnsureTstateNotNULL(tstate);
617649
struct _ceval_state *ceval = &tstate->interp->ceval;
618650
drop_gil(ceval, tstate);
619651
}
@@ -625,7 +657,7 @@ PyEval_AcquireThread(PyThreadState *tstate)
625657

626658
take_gil(tstate);
627659

628-
if (_PyThreadState_Swap(tstate->interp->runtime, tstate) != NULL) {
660+
if (_PyThreadState_SwapNoGIL(tstate) != NULL) {
629661
Py_FatalError("non-NULL old thread state");
630662
}
631663
}
@@ -635,8 +667,7 @@ PyEval_ReleaseThread(PyThreadState *tstate)
635667
{
636668
assert(is_tstate_valid(tstate));
637669

638-
_PyRuntimeState *runtime = tstate->interp->runtime;
639-
PyThreadState *new_tstate = _PyThreadState_Swap(runtime, NULL);
670+
PyThreadState *new_tstate = _PyThreadState_SwapNoGIL(NULL);
640671
if (new_tstate != tstate) {
641672
Py_FatalError("wrong thread state");
642673
}
@@ -684,8 +715,7 @@ _PyEval_SignalAsyncExc(PyInterpreterState *interp)
684715
PyThreadState *
685716
PyEval_SaveThread(void)
686717
{
687-
_PyRuntimeState *runtime = &_PyRuntime;
688-
PyThreadState *tstate = _PyThreadState_Swap(runtime, NULL);
718+
PyThreadState *tstate = _PyThreadState_SwapNoGIL(NULL);
689719
_Py_EnsureTstateNotNULL(tstate);
690720

691721
struct _ceval_state *ceval = &tstate->interp->ceval;
@@ -701,7 +731,7 @@ PyEval_RestoreThread(PyThreadState *tstate)
701731

702732
take_gil(tstate);
703733

704-
_PyThreadState_Swap(tstate->interp->runtime, tstate);
734+
_PyThreadState_SwapNoGIL(tstate);
705735
}
706736

707737

@@ -1005,7 +1035,7 @@ _Py_HandlePending(PyThreadState *tstate)
10051035
/* GIL drop request */
10061036
if (_Py_atomic_load_relaxed_int32(&interp_ceval_state->gil_drop_request)) {
10071037
/* Give another thread a chance */
1008-
if (_PyThreadState_Swap(runtime, NULL) != tstate) {
1038+
if (_PyThreadState_SwapNoGIL(NULL) != tstate) {
10091039
Py_FatalError("tstate mix-up");
10101040
}
10111041
drop_gil(interp_ceval_state, tstate);
@@ -1014,7 +1044,7 @@ _Py_HandlePending(PyThreadState *tstate)
10141044

10151045
take_gil(tstate);
10161046

1017-
if (_PyThreadState_Swap(runtime, tstate) != NULL) {
1047+
if (_PyThreadState_SwapNoGIL(tstate) != NULL) {
10181048
Py_FatalError("orphan tstate");
10191049
}
10201050
}

0 commit comments

Comments
 (0)