1//! Implementation details of `#[pymodule]` which need to be accessible from proc-macro generated code.
2
3use std::cell::UnsafeCell;
4
5#[cfg(all(not(PyPy), Py_3_9, not(all(windows, Py_LIMITED_API, not(Py_3_10)))))]
6use portable_atomic::{AtomicI64, Ordering};
7
8#[cfg(not(PyPy))]
9use crate::exceptions::PyImportError;
10use crate::{ffi, sync::GILOnceCell, types::PyModule, Py, PyResult, Python};
11
12/// `Sync` wrapper of `ffi::PyModuleDef`.
13pub struct ModuleDef {
14 // wrapped in UnsafeCell so that Rust compiler treats this as interior mutability
15 ffi_def: UnsafeCell<ffi::PyModuleDef>,
16 initializer: ModuleInitializer,
17 /// Interpreter ID where module was initialized (not applicable on PyPy).
18 #[cfg(all(not(PyPy), Py_3_9, not(all(windows, Py_LIMITED_API, not(Py_3_10)))))]
19 interpreter: AtomicI64,
20 /// Initialized module object, cached to avoid reinitialization.
21 module: GILOnceCell<Py<PyModule>>,
22}
23
24/// Wrapper to enable initializer to be used in const fns.
25pub struct ModuleInitializer(pub for<'py> fn(Python<'py>, &PyModule) -> PyResult<()>);
26
27unsafe impl Sync for ModuleDef {}
28
29impl ModuleDef {
30 /// Make new module definition with given module name.
31 ///
32 /// # Safety
33 /// `name` and `doc` must be null-terminated strings.
34 pub const unsafe fn new(
35 name: &'static str,
36 doc: &'static str,
37 initializer: ModuleInitializer,
38 ) -> Self {
39 const INIT: ffi::PyModuleDef = ffi::PyModuleDef {
40 m_base: ffi::PyModuleDef_HEAD_INIT,
41 m_name: std::ptr::null(),
42 m_doc: std::ptr::null(),
43 m_size: 0,
44 m_methods: std::ptr::null_mut(),
45 m_slots: std::ptr::null_mut(),
46 m_traverse: None,
47 m_clear: None,
48 m_free: None,
49 };
50
51 let ffi_def = UnsafeCell::new(ffi::PyModuleDef {
52 m_name: name.as_ptr() as *const _,
53 m_doc: doc.as_ptr() as *const _,
54 ..INIT
55 });
56
57 ModuleDef {
58 ffi_def,
59 initializer,
60 // -1 is never expected to be a valid interpreter ID
61 #[cfg(all(not(PyPy), Py_3_9, not(all(windows, Py_LIMITED_API, not(Py_3_10)))))]
62 interpreter: AtomicI64::new(-1),
63 module: GILOnceCell::new(),
64 }
65 }
66 /// Builds a module using user given initializer. Used for [`#[pymodule]`][crate::pymodule].
67 pub fn make_module(&'static self, py: Python<'_>) -> PyResult<Py<PyModule>> {
68 #[cfg(all(PyPy, not(Py_3_8)))]
69 {
70 const PYPY_GOOD_VERSION: [u8; 3] = [7, 3, 8];
71 let version = py
72 .import("sys")?
73 .getattr("implementation")?
74 .getattr("version")?;
75 if version.lt(crate::types::PyTuple::new(py, PYPY_GOOD_VERSION))? {
76 let warn = py.import("warnings")?.getattr("warn")?;
77 warn.call1((
78 "PyPy 3.7 versions older than 7.3.8 are known to have binary \
79 compatibility issues which may cause segfaults. Please upgrade.",
80 ))?;
81 }
82 }
83 // Check the interpreter ID has not changed, since we currently have no way to guarantee
84 // that static data is not reused across interpreters.
85 //
86 // PyPy does not have subinterpreters, so no need to check interpreter ID.
87 #[cfg(not(PyPy))]
88 {
89 // PyInterpreterState_Get is only available on 3.9 and later, but is missing
90 // from python3.dll for Windows stable API on 3.9
91 #[cfg(all(Py_3_9, not(all(windows, Py_LIMITED_API, not(Py_3_10)))))]
92 {
93 let current_interpreter =
94 unsafe { ffi::PyInterpreterState_GetID(ffi::PyInterpreterState_Get()) };
95 crate::err::error_on_minusone(py, current_interpreter)?;
96 if let Err(initialized_interpreter) = self.interpreter.compare_exchange(
97 -1,
98 current_interpreter,
99 Ordering::SeqCst,
100 Ordering::SeqCst,
101 ) {
102 if initialized_interpreter != current_interpreter {
103 return Err(PyImportError::new_err(
104 "PyO3 modules do not yet support subinterpreters, see https://github.com/PyO3/pyo3/issues/576",
105 ));
106 }
107 }
108 }
109 #[cfg(not(all(Py_3_9, not(all(windows, Py_LIMITED_API, not(Py_3_10))))))]
110 {
111 // CPython before 3.9 does not have APIs to check the interpreter ID, so best that can be
112 // done to guard against subinterpreters is fail if the module is initialized twice
113 if self.module.get(py).is_some() {
114 return Err(PyImportError::new_err(
115 "PyO3 modules compiled for CPython 3.8 or older may only be initialized once per interpreter process"
116 ));
117 }
118 }
119 }
120 self.module
121 .get_or_try_init(py, || {
122 let module = unsafe {
123 Py::<PyModule>::from_owned_ptr_or_err(
124 py,
125 ffi::PyModule_Create(self.ffi_def.get()),
126 )?
127 };
128 (self.initializer.0)(py, module.as_ref(py))?;
129 Ok(module)
130 })
131 .map(|py_module| py_module.clone_ref(py))
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use std::sync::atomic::{AtomicBool, Ordering};
138
139 use crate::{types::PyModule, PyResult, Python};
140
141 use super::{ModuleDef, ModuleInitializer};
142
143 #[test]
144 fn module_init() {
145 static MODULE_DEF: ModuleDef = unsafe {
146 ModuleDef::new(
147 "test_module\0",
148 "some doc\0",
149 ModuleInitializer(|_, m| {
150 m.add("SOME_CONSTANT", 42)?;
151 Ok(())
152 }),
153 )
154 };
155 Python::with_gil(|py| {
156 let module = MODULE_DEF.make_module(py).unwrap().into_ref(py);
157 assert_eq!(
158 module
159 .getattr("__name__")
160 .unwrap()
161 .extract::<&str>()
162 .unwrap(),
163 "test_module",
164 );
165 assert_eq!(
166 module
167 .getattr("__doc__")
168 .unwrap()
169 .extract::<&str>()
170 .unwrap(),
171 "some doc",
172 );
173 assert_eq!(
174 module
175 .getattr("SOME_CONSTANT")
176 .unwrap()
177 .extract::<u8>()
178 .unwrap(),
179 42,
180 );
181 })
182 }
183
184 #[test]
185 fn module_def_new() {
186 // To get coverage for ModuleDef::new() need to create a non-static ModuleDef, however init
187 // etc require static ModuleDef, so this test needs to be separated out.
188 static NAME: &str = "test_module\0";
189 static DOC: &str = "some doc\0";
190
191 static INIT_CALLED: AtomicBool = AtomicBool::new(false);
192
193 #[allow(clippy::unnecessary_wraps)]
194 fn init(_: Python<'_>, _: &PyModule) -> PyResult<()> {
195 INIT_CALLED.store(true, Ordering::SeqCst);
196 Ok(())
197 }
198
199 unsafe {
200 let module_def: ModuleDef = ModuleDef::new(NAME, DOC, ModuleInitializer(init));
201 assert_eq!((*module_def.ffi_def.get()).m_name, NAME.as_ptr() as _);
202 assert_eq!((*module_def.ffi_def.get()).m_doc, DOC.as_ptr() as _);
203
204 Python::with_gil(|py| {
205 module_def.initializer.0(py, py.import("builtins").unwrap()).unwrap();
206 assert!(INIT_CALLED.load(Ordering::SeqCst));
207 })
208 }
209 }
210}
211