Skip to content

Commit 78ba70d

Browse files
committed
pymodule: only allow initializing once per process
1 parent 10a87ac commit 78ba70d

File tree

5 files changed

+59
-14
lines changed

5 files changed

+59
-14
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5252
- `PyCapsule::set_context` no longer takes a `py: Python<'_>` argument.
5353
- `PyCapsule::name` now returns `PyResult<Option<&CStr>>` instead of `&CStr`.
5454
- `FromPyObject::extract` now raises an error if source type is `PyString` and target type is `Vec<T>`. [#2500](https://github.com/PyO3/pyo3/pull/2500)
55-
- `pyo3_build_config::add_extension_module_link_args()` now also emits linker arguments for `wasm32-unknown-emscripten`. [#2500](https://github.com/PyO3/pyo3/pull/2500)
55+
- Only allow each `#[pymodule]` to be initialized once. [#2523](https://github.com/PyO3/pyo3/pull/2523)
56+
- `pyo3_build_config::add_extension_module_link_args()` now also emits linker arguments for `wasm32-unknown-emscripten`. [#2538](https://github.com/PyO3/pyo3/pull/2538)
5657

5758
### Removed
5859

guide/src/migration.md

+4
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ fn get_type_object<T: PyTypeInfo>(py: Python<'_>) -> &PyType {
7777

7878
If this leads to errors, simply implement `IntoPy`. Because pyclasses already implement `IntoPy`, you probably don't need to worry about this.
7979

80+
### Each `#[pymodule]` can now only be initialized once per process
81+
82+
To make PyO3 modules sound in the presence of Python sub-interpreters, for now it has been necessary to explicitly disable the ability to initialize a `#[pymodule]` more than once in the same process. Attempting to do this will now raise an `ImportError`.
83+
8084
## from 0.15.* to 0.16
8185

8286
### Drop support for older technologies

pytests/tests/test_misc.py

+18
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
1+
import importlib
2+
import platform
3+
14
import pyo3_pytests.misc
5+
import pytest
26

37

48
def test_issue_219():
59
# Should not deadlock
610
pyo3_pytests.misc.issue_219()
11+
12+
13+
@pytest.mark.skipif(
14+
platform.python_implementation() == "PyPy",
15+
reason="PyPy does not reinitialize the module (appears to be some internal caching)",
16+
)
17+
def test_second_module_import_fails():
18+
spec = importlib.util.find_spec("pyo3_pytests.pyo3_pytests")
19+
20+
with pytest.raises(
21+
ImportError,
22+
match="PyO3 modules may only be initialized once per interpreter process",
23+
):
24+
importlib.util.module_from_spec(spec)

src/impl_/pymodule.rs

+13-3
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
//! Implementation details of `#[pymodule]` which need to be accessible from proc-macro generated code.
22
3-
use std::cell::UnsafeCell;
3+
use std::{
4+
cell::UnsafeCell,
5+
sync::atomic::{self, AtomicBool},
6+
};
47

58
use crate::{
6-
callback::panic_result_into_callback_output, ffi, types::PyModule, GILPool, IntoPyPointer, Py,
7-
PyObject, PyResult, Python,
9+
callback::panic_result_into_callback_output, exceptions::PyImportError, ffi, types::PyModule,
10+
GILPool, IntoPyPointer, Py, PyObject, PyResult, Python,
811
};
912

1013
/// `Sync` wrapper of `ffi::PyModuleDef`.
1114
pub struct ModuleDef {
1215
// wrapped in UnsafeCell so that Rust compiler treats this as interior mutability
1316
ffi_def: UnsafeCell<ffi::PyModuleDef>,
1417
initializer: ModuleInitializer,
18+
initialized: AtomicBool,
1519
}
1620

1721
/// Wrapper to enable initializer to be used in const fns.
@@ -50,13 +54,19 @@ impl ModuleDef {
5054
ModuleDef {
5155
ffi_def,
5256
initializer,
57+
initialized: AtomicBool::new(false),
5358
}
5459
}
5560
/// Builds a module using user given initializer. Used for [`#[pymodule]`][crate::pymodule].
5661
pub fn make_module(&'static self, py: Python<'_>) -> PyResult<PyObject> {
5762
let module = unsafe {
5863
Py::<PyModule>::from_owned_ptr_or_err(py, ffi::PyModule_Create(self.ffi_def.get()))?
5964
};
65+
if self.initialized.swap(true, atomic::Ordering::SeqCst) {
66+
return Err(PyImportError::new_err(
67+
"PyO3 modules may only be initialized once per interpreter process",
68+
));
69+
}
6070
(self.initializer.0)(py, module.as_ref(py))?;
6171
Ok(module.into())
6272
}

tests/test_module.rs

+22-10
Original file line numberDiff line numberDiff line change
@@ -184,17 +184,16 @@ fn custom_named_fn() -> usize {
184184
42
185185
}
186186

187-
#[pymodule]
188-
fn foobar_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
189-
m.add_function(wrap_pyfunction!(custom_named_fn, m)?)?;
190-
m.dict().set_item("yay", "me")?;
191-
Ok(())
192-
}
193-
194187
#[test]
195188
fn test_custom_names() {
189+
#[pymodule]
190+
fn custom_names(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
191+
m.add_function(wrap_pyfunction!(custom_named_fn, m)?)?;
192+
Ok(())
193+
}
194+
196195
Python::with_gil(|py| {
197-
let module = pyo3::wrap_pymodule!(foobar_module)(py);
196+
let module = pyo3::wrap_pymodule!(custom_names)(py);
198197

199198
py_assert!(py, module, "not hasattr(module, 'custom_named_fn')");
200199
py_assert!(py, module, "module.foobar() == 42");
@@ -203,8 +202,14 @@ fn test_custom_names() {
203202

204203
#[test]
205204
fn test_module_dict() {
205+
#[pymodule]
206+
fn module_dict(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
207+
m.dict().set_item("yay", "me")?;
208+
Ok(())
209+
}
210+
206211
Python::with_gil(|py| {
207-
let module = pyo3::wrap_pymodule!(foobar_module)(py);
212+
let module = pyo3::wrap_pymodule!(module_dict)(py);
208213

209214
py_assert!(py, module, "module.yay == 'me'");
210215
});
@@ -213,7 +218,14 @@ fn test_module_dict() {
213218
#[test]
214219
fn test_module_dunder_all() {
215220
Python::with_gil(|py| {
216-
let module = pyo3::wrap_pymodule!(foobar_module)(py);
221+
#[pymodule]
222+
fn dunder_all(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
223+
m.dict().set_item("yay", "me")?;
224+
m.add_function(wrap_pyfunction!(custom_named_fn, m)?)?;
225+
Ok(())
226+
}
227+
228+
let module = pyo3::wrap_pymodule!(dunder_all)(py);
217229

218230
py_assert!(py, module, "module.__all__ == ['foobar']");
219231
});

0 commit comments

Comments
 (0)