mirror of
https://github.com/pybind/pybind11.git
synced 2026-04-20 06:49:25 +00:00
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:
@@ -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"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user