Fix concurrency consistency for internals_pp_manager under multiple-interpreters (#5947)

* Add per-interpreter storage for `gil_safe_call_once_and_store`

* Disable thread local cache for `internals_pp_manager`

* Disable thread local cache for `internals_pp_manager` for multi-interpreter only

* Use anonymous namespace to separate these type_ids from other tests with the same class names.

* style: pre-commit fixes

* Revert internals_pp_manager changes

* This is the crux of fix for the subinterpreter_before_main failure.

The pre_init needs to check if it is in a subinterpreter or not. But in 3.13+ this static initializer runs in the main interpreter.  So we need to check this later, during the exec phase.

* Continue to do the ensure in both places, there might be a reason it was where it was...

Should not hurt anything to do it extra times here.

* Change get_num_interpreters_seen to a boolean flag instead.

The count was not used, it was just checked for > 1, we now accomplish this by setting the flag.

* Spelling typo

* Work around older python versions, only need this check for newish versions

* Add more comments for test case

* Add more comments for test case

* Stop traceback propagation

* Re-enable subinterpreter support on ubuntu 3.14 builds

Was disabled in e4873e8

* As suggested, don't use an anonymous namespace.

* Typo in test assert format string

* Use a more appropriate function name

* Fix mod_per_interpreter_gil* output directory on Windows/MSVC

On Windows with MSVC (multi-configuration generators), CMake uses
config-specific properties like LIBRARY_OUTPUT_DIRECTORY_DEBUG when
set, otherwise falls back to LIBRARY_OUTPUT_DIRECTORY/<Config>/.

The main test modules (pybind11_tests, etc.) correctly set both
LIBRARY_OUTPUT_DIRECTORY and the config-specific variants (lines
517-528), so they output directly to tests/.

However, the mod_per_interpreter_gil* modules only copied the base
LIBRARY_OUTPUT_DIRECTORY property, causing them to be placed in
tests/Debug/ instead of tests/.

This mismatch caused test_import_in_subinterpreter_concurrently and
related tests to fail with ModuleNotFoundError on Windows Python 3.14,
because the test code sets sys.path based on pybind11_tests.__file__
(which is in tests/) but tries to import mod_per_interpreter_gil_with_singleton
(which ended up in tests/Debug/).

This bug was previously masked by @pytest.mark.xfail decorators on
these tests. Now that the underlying "Duplicate C++ type registration"
issue is fixed and the xfails are removed, this path issue surfaced.

The fix mirrors the same pattern used for main test targets: also set
LIBRARY_OUTPUT_DIRECTORY_<CONFIG> for each configuration type.

* Remove unneeded `pytest.importorskip`

* Remove comment

---------

Co-authored-by: b-pass <b-pass@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Ralf W. Grosse-Kunstleve <rgrossekunst@nvidia.com>
This commit is contained in:
Xuehai Pan
2025-12-27 02:59:11 +08:00
committed by GitHub
parent 0057e4945d
commit fee2527dfa
10 changed files with 72 additions and 56 deletions

View File

@@ -90,7 +90,7 @@ def test_independent_subinterpreters():
run_string, create = get_interpreters(modern=True)
m = pytest.importorskip("mod_per_interpreter_gil")
import mod_per_interpreter_gil as m
if not m.defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT:
pytest.skip("Does not have subinterpreter support compiled in")
@@ -139,7 +139,7 @@ def test_independent_subinterpreters_modern():
sys.path.insert(0, os.path.dirname(pybind11_tests.__file__))
m = pytest.importorskip("mod_per_interpreter_gil")
import mod_per_interpreter_gil as m
if not m.defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT:
pytest.skip("Does not have subinterpreter support compiled in")
@@ -187,7 +187,7 @@ def test_dependent_subinterpreters():
run_string, create = get_interpreters(modern=False)
m = pytest.importorskip("mod_shared_interpreter_gil")
import mod_shared_interpreter_gil as m
if not m.defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT:
pytest.skip("Does not have subinterpreter support compiled in")
@@ -222,15 +222,27 @@ PREAMBLE_CODE = textwrap.dedent(
objects = m.get_objects_in_singleton()
expected = [
type(None),
tuple,
list,
dict,
collections.OrderedDict,
collections.defaultdict,
collections.deque,
type(None), # static type: shared between interpreters
tuple, # static type: shared between interpreters
list, # static type: shared between interpreters
dict, # static type: shared between interpreters
collections.OrderedDict, # static type: shared between interpreters
collections.defaultdict, # heap type: dynamically created per interpreter
collections.deque, # heap type: dynamically created per interpreter
]
assert objects == expected, f"Expected {{expected!r}}, got {{objects!r}}."
# Check that we have the expected objects. Avoid IndexError by checking lengths first.
assert len(objects) == len(expected), (
f"Expected {{expected!r}} ({{len(expected)}}), got {{objects!r}} ({{len(objects)}})."
)
# The first ones are static types shared between interpreters.
assert objects[:-2] == expected[:-2], (
f"Expected static objects {{expected[:-2]!r}}, got {{objects[:-2]!r}}."
)
# The last two are heap types created per-interpreter.
# The expected objects are dynamically imported from `collections`.
assert objects[-2:] == expected[-2:], (
f"Expected heap objects {{expected[-2:]!r}}, got {{objects[-2:]!r}}."
)
assert hasattr(m, 'MyClass'), "Module missing MyClass"
assert hasattr(m, 'MyGlobalError'), "Module missing MyGlobalError"
@@ -240,11 +252,6 @@ PREAMBLE_CODE = textwrap.dedent(
).lstrip()
@pytest.mark.xfail(
reason="Duplicate C++ type registration under multiple-interpreters, needs investigation.",
# raises=interpreters.ExecutionFailed, # need to import the module
strict=False,
)
@pytest.mark.skipif(
sys.platform.startswith("emscripten"), reason="Requires loadable modules"
)
@@ -278,14 +285,9 @@ def check_script_success_in_subprocess(code: str, *, rerun: int = 8) -> None:
f"```\n\n"
f"Output:\n"
f"{ex.output}"
) from ex
) from None
@pytest.mark.xfail(
reason="Duplicate C++ type registration under multiple-interpreters, needs investigation.",
raises=RuntimeError,
strict=False,
)
@pytest.mark.skipif(
sys.platform.startswith("emscripten"), reason="Requires loadable modules"
)
@@ -342,11 +344,6 @@ def test_import_in_subinterpreter_after_main():
)
@pytest.mark.xfail(
reason="Duplicate C++ type registration under multiple-interpreters, needs investigation.",
raises=RuntimeError,
strict=False,
)
@pytest.mark.skipif(
sys.platform.startswith("emscripten"), reason="Requires loadable modules"
)
@@ -427,11 +424,6 @@ def test_import_in_subinterpreter_before_main():
)
@pytest.mark.xfail(
reason="Duplicate C++ type registration under multiple-interpreters, needs investigation.",
raises=RuntimeError,
strict=False,
)
@pytest.mark.skipif(
sys.platform.startswith("emscripten"), reason="Requires loadable modules"
)