Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions Include/internal/pycore_import.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,18 @@ PyAPI_FUNC(int) _PyImport_ClearExtension(PyObject *name, PyObject *filename);
// state of the module argument:
// - If module is NULL or a PyModuleObject with md_gil == Py_MOD_GIL_NOT_USED,
// call _PyEval_DisableGIL().
// - Otherwise, call _PyEval_EnableGILPermanent(). If the GIL was not already
// enabled permanently, issue a warning referencing the module's name.
// - Otherwise, call _PyImport_EnableGILAndWarn
//
// This function may raise an exception.
extern int _PyImport_CheckGILForModule(PyObject *module, PyObject *module_name);
// Assuming that the GIL is enabled from a call to
// _PyEval_EnableGILTransient(), call _PyEval_EnableGILPermanent().
// If the GIL was not already enabled permanently, issue a warning referencing
// the module's name.
// Leave a message in verbose mode.
//
// This function may raise an exception.
extern int _PyImport_EnableGILAndWarn(PyThreadState *, PyObject *module_name);
#endif

#ifdef __cplusplus
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_capi/test_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import unittest
import types
from test.support import import_helper, subTests
from test.support import import_helper, subTests, requires_gil_enabled

# Skip this test if the _testcapi module isn't available.
_testcapi = import_helper.import_module('_testcapi')
Expand All @@ -25,6 +25,7 @@ def def_and_token(mod):
)

class TestModFromSlotsAndSpec(unittest.TestCase):
@requires_gil_enabled("empty slots re-enable GIL")
def test_empty(self):
mod = _testcapi.module_from_slots_empty(FakeSpec())
self.assertIsInstance(mod, types.ModuleType)
Expand Down
69 changes: 69 additions & 0 deletions Lib/test/test_import/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,10 @@
_testmultiphase = None
try:
import _interpreters
import concurrent.interpreters
except ModuleNotFoundError:
_interpreters = None
concurrent = None
try:
import _testinternalcapi
except ImportError:
Expand Down Expand Up @@ -3407,6 +3409,39 @@ def test_from_modexport(self):

self.assertEqual(module.__name__, modname)

@requires_subinterpreters
def test_from_modexport_gil_used(self):
# Test that a module with Py_MOD_GIL_USED (re-)enables the GIL.
# Do this in a new interpreter to avoid interfering with global state.
modname = '_test_from_modexport_gil_used'
filename = _testmultiphase.__file__
interp = concurrent.interpreters.create()
self.addCleanup(interp.close)
queue = concurrent.interpreters.create_queue()
interp.prepare_main(
modname=modname,
filename=filename,
queue=queue,
)
enabled_before = sys._is_gil_enabled()
interp.exec(f"""if True:
import sys
from test.support.warnings_helper import check_warnings
from {__name__} import import_extension_from_file
with check_warnings((".*GIL..has been enabled.*", RuntimeWarning),
quiet=True):
module = import_extension_from_file(modname, filename,
put_in_sys_modules=False)
queue.put(module.__name__)
queue.put(sys._is_gil_enabled())
""")

self.assertEqual(queue.get(), modname)
self.assertEqual(queue.get(), True)
self.assertTrue(queue.empty())

self.assertEqual(enabled_before, sys._is_gil_enabled())

def test_from_modexport_null(self):
modname = '_test_from_modexport_null'
filename = _testmultiphase.__file__
Expand All @@ -3428,6 +3463,39 @@ def test_from_modexport_create_nonmodule(self):
put_in_sys_modules=False)
self.assertIsInstance(module, str)

@requires_subinterpreters
def test_from_modexport_create_nonmodule_gil_used(self):
# Test that a module with Py_MOD_GIL_USED (re-)enables the GIL.
# Do this in a new interpreter to avoid interfering with global state.
modname = '_test_from_modexport_create_nonmodule_gil_used'
filename = _testmultiphase.__file__
interp = concurrent.interpreters.create()
self.addCleanup(interp.close)
queue = concurrent.interpreters.create_queue()
interp.prepare_main(
modname=modname,
filename=filename,
queue=queue,
)
enabled_before = sys._is_gil_enabled()
interp.exec(f"""if True:
import sys
from test.support.warnings_helper import check_warnings
from {__name__} import import_extension_from_file
with check_warnings((".*GIL..has been enabled.*", RuntimeWarning),
quiet=True):
module = import_extension_from_file(modname, filename,
put_in_sys_modules=False)
queue.put(module)
queue.put(sys._is_gil_enabled())
""")

self.assertIsInstance(queue.get(), str)
self.assertEqual(queue.get(), True)
self.assertTrue(queue.empty())

self.assertEqual(enabled_before, sys._is_gil_enabled())

def test_from_modexport_smoke(self):
# General positive test for sundry features
# (PyModule_FromSlotsAndSpec tests exercise these more carefully)
Expand Down Expand Up @@ -3456,6 +3524,7 @@ class Sub(tp):
pass
self.assertEqual(_testcapi.pytype_getmodulebytoken(Sub, token), module)

@requires_gil_enabled("empty slots re-enable GIL")
def test_from_modexport_empty_slots(self):
# Module to test that:
# - no slots are mandatory for PyModExport
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_importlib/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
import tempfile
import types

_testsinglephase = import_helper.import_module("_testsinglephase")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_testsinglephase is unusable in subinterpreters.

# gh-116303: Skip test module dependent tests if test modules are unavailable
import_helper.import_module("_testmultiphase")


BUILTINS = types.SimpleNamespace()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix :c:macro:`Py_mod_gil` with API added in :pep:`793`:
:c:func:`!PyModule_FromSlotsAndSpec` and ``PyModExport`` hooks
23 changes: 23 additions & 0 deletions Modules/_testcapi/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ module_from_slots_name(PyObject *self, PyObject *spec)
{
PyModuleDef_Slot slots[] = {
{Py_mod_name, "currently ignored..."},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0},
};
return PyModule_FromSlotsAndSpec(slots, spec);
Expand All @@ -37,6 +39,8 @@ module_from_slots_doc(PyObject *self, PyObject *spec)
{
PyModuleDef_Slot slots[] = {
{Py_mod_doc, "the docstring"},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0},
};
return PyModule_FromSlotsAndSpec(slots, spec);
Expand All @@ -47,6 +51,8 @@ module_from_slots_size(PyObject *self, PyObject *spec)
{
PyModuleDef_Slot slots[] = {
{Py_mod_state_size, (void*)123},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0},
};
PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec);
Expand All @@ -72,6 +78,8 @@ module_from_slots_methods(PyObject *self, PyObject *spec)
{
PyModuleDef_Slot slots[] = {
{Py_mod_methods, a_methoddef_array},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0},
};
return PyModule_FromSlotsAndSpec(slots, spec);
Expand All @@ -90,6 +98,8 @@ module_from_slots_gc(PyObject *self, PyObject *spec)
{Py_mod_state_traverse, noop_traverse},
{Py_mod_state_clear, noop_clear},
{Py_mod_state_free, noop_free},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0},
};
PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec);
Expand Down Expand Up @@ -118,6 +128,8 @@ module_from_slots_token(PyObject *self, PyObject *spec)
{
PyModuleDef_Slot slots[] = {
{Py_mod_token, (void*)&test_token},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0},
};
PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec);
Expand All @@ -144,6 +156,8 @@ module_from_slots_exec(PyObject *self, PyObject *spec)
{
PyModuleDef_Slot slots[] = {
{Py_mod_exec, simple_exec},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0},
};
PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec);
Expand Down Expand Up @@ -175,6 +189,8 @@ module_from_slots_create(PyObject *self, PyObject *spec)
{
PyModuleDef_Slot slots[] = {
{Py_mod_create, create_attr_from_spec},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0},
};
return PyModule_FromSlotsAndSpec(slots, spec);
Expand Down Expand Up @@ -205,6 +221,8 @@ module_from_slots_repeat_slot(PyObject *self, PyObject *spec)
PyModuleDef_Slot slots[] = {
{slot_id, "anything"},
{slot_id, "anything else"},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0},
};
return PyModule_FromSlotsAndSpec(slots, spec);
Expand All @@ -219,6 +237,8 @@ module_from_slots_null_slot(PyObject *self, PyObject *spec)
}
PyModuleDef_Slot slots[] = {
{slot_id, NULL},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0},
};
return PyModule_FromSlotsAndSpec(slots, spec);
Expand All @@ -233,6 +253,8 @@ module_from_def_slot(PyObject *self, PyObject *spec)
}
PyModuleDef_Slot slots[] = {
{slot_id, "anything"},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0},
};
PyModuleDef def = {
Expand Down Expand Up @@ -280,6 +302,7 @@ module_from_def_multiple_exec(PyObject *self, PyObject *spec)
static PyModuleDef_Slot slots[] = {
{Py_mod_exec, simple_exec},
{Py_mod_exec, another_exec},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0},
};
Expand Down
31 changes: 31 additions & 0 deletions Modules/_testmultiphase.c
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,20 @@ PyModExport__test_from_modexport(void)
{
static PyModuleDef_Slot slots[] = {
{Py_mod_name, "_test_from_modexport"},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0},
};
return slots;
}

PyMODEXPORT_FUNC
PyModExport__test_from_modexport_gil_used(void)
{
static PyModuleDef_Slot slots[] = {
{Py_mod_name, "_test_from_modexport_gil_used"},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_USED},
{0},
};
return slots;
Expand Down Expand Up @@ -1073,6 +1087,21 @@ PyModExport__test_from_modexport_create_nonmodule(void)
static PyModuleDef_Slot slots[] = {
{Py_mod_name, "_test_from_modexport_create_nonmodule"},
{Py_mod_create, modexport_create_string},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0},
};
return slots;
}

PyMODEXPORT_FUNC
PyModExport__test_from_modexport_create_nonmodule_gil_used(void)
{
static PyModuleDef_Slot slots[] = {
{Py_mod_name, "_test_from_modexport_create_nonmodule"},
{Py_mod_create, modexport_create_string},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_USED},
{0},
};
return slots;
Expand Down Expand Up @@ -1165,6 +1194,8 @@ PyModExport__test_from_modexport_smoke(void)
{Py_mod_methods, methods},
{Py_mod_state_free, modexport_smoke_free},
{Py_mod_token, (void*)&modexport_smoke_test_token},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0},
};
return slots;
Expand Down
17 changes: 17 additions & 0 deletions Objects/moduleobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "Python.h"
#include "pycore_call.h" // _PyObject_CallNoArgs()
#include "pycore_ceval.h" // _PyEval_EnableGILTransient()
#include "pycore_dict.h" // _PyDict_EnablePerThreadRefcounting()
#include "pycore_fileutils.h" // _Py_wgetcwd
#include "pycore_import.h" // _PyImport_GetNextModuleIndex()
Expand Down Expand Up @@ -522,6 +523,22 @@ module_from_def_and_spec(
#undef COPY_COMMON_SLOT
}

#ifdef Py_GIL_DISABLED
// For modules created directly from slots (not from a def), we enable
// the GIL here (pairing `_PyEval_EnableGILTransient` with
// an immediate `_PyImport_EnableGILAndWarn`).
// For modules created from a def, the caller is responsible for this.
if (!original_def && requires_gil) {
PyThreadState *tstate = _PyThreadState_GET();
if (_PyEval_EnableGILTransient(tstate) < 0) {
goto error;
}
if (_PyImport_EnableGILAndWarn(tstate, nameobj) < 0) {
goto error;
}
}
#endif

/* By default, multi-phase init modules are expected
to work under multiple interpreters. */
if (!has_multiple_interpreters_slot) {
Expand Down
Loading
Loading