1 | //! Implementation details of `#[pymodule]` which need to be accessible from proc-macro generated code. |
2 | |
3 | use std::cell::UnsafeCell; |
4 | |
5 | #[cfg (all(not(PyPy), Py_3_9, not(all(windows, Py_LIMITED_API, not(Py_3_10)))))] |
6 | use portable_atomic::{AtomicI64, Ordering}; |
7 | |
8 | #[cfg (not(PyPy))] |
9 | use crate::exceptions::PyImportError; |
10 | use crate::{ffi, sync::GILOnceCell, types::PyModule, Py, PyResult, Python}; |
11 | |
12 | /// `Sync` wrapper of `ffi::PyModuleDef`. |
13 | pub 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. |
25 | pub struct ModuleInitializer(pub for<'py> fn(Python<'py>, &PyModule) -> PyResult<()>); |
26 | |
27 | unsafe impl Sync for ModuleDef {} |
28 | |
29 | impl 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)] |
136 | mod 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 | |