Skip to content

Commit f5d6b6d

Browse files
committed
relax multiple-import check to only prevent subinterpreters
1 parent bcb0104 commit f5d6b6d

File tree

4 files changed

+53
-14
lines changed

4 files changed

+53
-14
lines changed

newsfragments/3447.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Relax multiple import check to only prevent use of subinterpreters.

pyo3-build-config/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ pub fn print_feature_cfgs() {
146146
if rustc_minor_version >= 59 {
147147
println!("cargo:rustc-cfg=thread_local_const_init");
148148
}
149+
150+
// Enable use of OnceLock on Rust 1.70 and greater
151+
if rustc_minor_version >= 70 {
152+
println!("cargo:rustc-cfg=once_lock");
153+
}
149154
}
150155

151156
/// Private exports used in PyO3's build.rs

pytests/tests/test_misc.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,27 @@ def test_issue_219():
1010
pyo3_pytests.misc.issue_219()
1111

1212

13+
def test_multiple_imports_same_interpreter_ok():
14+
spec = importlib.util.find_spec("pyo3_pytests.pyo3_pytests")
15+
16+
module = importlib.util.module_from_spec(spec)
17+
assert dir(module) == dir(pyo3_pytests.pyo3_pytests)
18+
19+
1320
@pytest.mark.skipif(
1421
platform.python_implementation() == "PyPy",
15-
reason="PyPy does not reinitialize the module (appears to be some internal caching)",
22+
reason="PyPy does not support subinterpreters",
1623
)
17-
def test_second_module_import_fails():
18-
spec = importlib.util.find_spec("pyo3_pytests.pyo3_pytests")
24+
def test_import_in_subinterpreter_forbidden():
25+
import _xxsubinterpreters
1926

27+
sub_interpreter = _xxsubinterpreters.create()
2028
with pytest.raises(
21-
ImportError,
22-
match="PyO3 modules may only be initialized once per interpreter process",
29+
_xxsubinterpreters.RunFailedError,
30+
match="PyO3 modules do not yet support subinterpreters, see https://github.com/PyO3/pyo3/issues/576",
2331
):
24-
importlib.util.module_from_spec(spec)
32+
_xxsubinterpreters.run_string(
33+
sub_interpreter, "import pyo3_pytests.pyo3_pytests"
34+
)
35+
36+
_xxsubinterpreters.destroy(sub_interpreter)

src/impl_/pymodule.rs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
//! Implementation details of `#[pymodule]` which need to be accessible from proc-macro generated code.
22
3-
use std::{
4-
cell::UnsafeCell,
5-
sync::atomic::{self, AtomicBool},
6-
};
3+
use std::cell::UnsafeCell;
4+
#[cfg(once_lock)]
5+
use std::sync::OnceLock;
6+
7+
#[cfg(not(once_lock))]
8+
use parking_lot::Mutex;
79

810
use crate::{exceptions::PyImportError, ffi, types::PyModule, Py, PyResult, Python};
911

@@ -12,7 +14,10 @@ pub struct ModuleDef {
1214
// wrapped in UnsafeCell so that Rust compiler treats this as interior mutability
1315
ffi_def: UnsafeCell<ffi::PyModuleDef>,
1416
initializer: ModuleInitializer,
15-
initialized: AtomicBool,
17+
#[cfg(once_lock)]
18+
interpreter: OnceLock<i64>,
19+
#[cfg(not(once_lock))]
20+
interpreter: Mutex<Option<i64>>,
1621
}
1722

1823
/// Wrapper to enable initializer to be used in const fns.
@@ -51,7 +56,10 @@ impl ModuleDef {
5156
ModuleDef {
5257
ffi_def,
5358
initializer,
54-
initialized: AtomicBool::new(false),
59+
#[cfg(once_lock)]
60+
interpreter: OnceLock::new(),
61+
#[cfg(not(once_lock))]
62+
interpreter: Mutex::new(None),
5563
}
5664
}
5765
/// Builds a module using user given initializer. Used for [`#[pymodule]`][crate::pymodule].
@@ -74,9 +82,22 @@ impl ModuleDef {
7482
let module = unsafe {
7583
Py::<PyModule>::from_owned_ptr_or_err(py, ffi::PyModule_Create(self.ffi_def.get()))?
7684
};
77-
if self.initialized.swap(true, atomic::Ordering::SeqCst) {
85+
let current_interpreter =
86+
unsafe { ffi::PyInterpreterState_GetID(ffi::PyInterpreterState_Get()) };
87+
let initialized_interpreter = py.allow_threads(|| {
88+
#[cfg(once_lock)]
89+
{
90+
*self.interpreter.get_or_init(|| current_interpreter)
91+
}
92+
93+
#[cfg(not(once_lock))]
94+
{
95+
*self.interpreter.lock().get_or_insert(current_interpreter)
96+
}
97+
});
98+
if current_interpreter != initialized_interpreter {
7899
return Err(PyImportError::new_err(
79-
"PyO3 modules may only be initialized once per interpreter process",
100+
"PyO3 modules do not yet support subinterpreters, see https://github.com/PyO3/pyo3/issues/576",
80101
));
81102
}
82103
(self.initializer.0)(py, module.as_ref(py))?;

0 commit comments

Comments
 (0)