gh-116322: Add Py_mod_gil module slot (#116882)

This PR adds the ability to enable the GIL if it was disabled at
interpreter startup, and modifies the multi-phase module initialization
path to enable the GIL when loading a module, unless that module's spec
includes a slot indicating it can run safely without the GIL.

PEP 703 called the constant for the slot `Py_mod_gil_not_used`; I went
with `Py_MOD_GIL_NOT_USED` for consistency with gh-104148.

A warning will be issued up to once per interpreter for the first
GIL-using module that is loaded. If `-v` is given, a shorter message
will be printed to stderr every time a GIL-using module is loaded
(including the first one that issues a warning).
This commit is contained in:
Brett Simmers 2024-05-03 08:30:55 -07:00 committed by GitHub
parent 3e818afb9b
commit c2627d6eea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
123 changed files with 376 additions and 62 deletions

View file

@ -0,0 +1,44 @@
import types
import unittest
from test.test_importlib import util
machinery = util.import_importlib('importlib.machinery')
from test.test_importlib.extension.test_loader import MultiPhaseExtensionModuleTests
class NonModuleExtensionTests:
setUp = MultiPhaseExtensionModuleTests.setUp
load_module_by_name = MultiPhaseExtensionModuleTests.load_module_by_name
def _test_nonmodule(self):
# Test returning a non-module object from create works.
name = self.name + '_nonmodule'
mod = self.load_module_by_name(name)
self.assertNotEqual(type(mod), type(unittest))
self.assertEqual(mod.three, 3)
# issue 27782
def test_nonmodule_with_methods(self):
# Test creating a non-module object with methods defined.
name = self.name + '_nonmodule_with_methods'
mod = self.load_module_by_name(name)
self.assertNotEqual(type(mod), type(unittest))
self.assertEqual(mod.three, 3)
self.assertEqual(mod.bar(10, 1), 9)
def test_null_slots(self):
# Test that NULL slots aren't a problem.
name = self.name + '_null_slots'
module = self.load_module_by_name(name)
self.assertIsInstance(module, types.ModuleType)
self.assertEqual(module.__name__, name)
(Frozen_NonModuleExtensionTests,
Source_NonModuleExtensionTests
) = util.test_both(NonModuleExtensionTests, machinery=machinery)
if __name__ == '__main__':
unittest.main()

View file

@ -10,7 +10,8 @@ import unittest
import warnings
import importlib.util
import importlib
from test.support import MISSING_C_DOCSTRINGS
from test import support
from test.support import MISSING_C_DOCSTRINGS, script_helper
class LoaderTests:
@ -325,29 +326,6 @@ class MultiPhaseExtensionModuleTests(abc.LoaderTests):
self.load_module_by_name(name)
self.assertEqual(cm.exception.name, name)
def test_nonmodule(self):
# Test returning a non-module object from create works.
name = self.name + '_nonmodule'
mod = self.load_module_by_name(name)
self.assertNotEqual(type(mod), type(unittest))
self.assertEqual(mod.three, 3)
# issue 27782
def test_nonmodule_with_methods(self):
# Test creating a non-module object with methods defined.
name = self.name + '_nonmodule_with_methods'
mod = self.load_module_by_name(name)
self.assertNotEqual(type(mod), type(unittest))
self.assertEqual(mod.three, 3)
self.assertEqual(mod.bar(10, 1), 9)
def test_null_slots(self):
# Test that NULL slots aren't a problem.
name = self.name + '_null_slots'
module = self.load_module_by_name(name)
self.assertIsInstance(module, types.ModuleType)
self.assertEqual(module.__name__, name)
def test_bad_modules(self):
# Test SystemError is raised for misbehaving extensions.
for name_base in [
@ -401,5 +379,14 @@ class MultiPhaseExtensionModuleTests(abc.LoaderTests):
) = util.test_both(MultiPhaseExtensionModuleTests, machinery=machinery)
class NonModuleExtensionTests(unittest.TestCase):
def test_nonmodule_cases(self):
# The test cases in this file cause the GIL to be enabled permanently
# in free-threaded builds, so they are run in a subprocess to isolate
# this effect.
script = support.findfile("test_importlib/extension/_test_nonmodule_cases.py")
script_helper.run_test_script(script)
if __name__ == '__main__':
unittest.main()