##// END OF EJS Templates
rust-pyo3: getting rust-cpython GIL handle from various PyO3 objects...
Georges Racinet -
r53425:1dd673c1 default
parent child Browse files
Show More
@@ -1,195 +1,214
1 //! This module takes care of all conversions involving `rusthg` (hg-cpython)
1 //! This module takes care of all conversions involving `rusthg` (hg-cpython)
2 //! objects in the PyO3 call context.
2 //! objects in the PyO3 call context.
3 //!
3 //!
4 //! For source code clarity, we only import (`use`) [`cpython`] traits and not
4 //! For source code clarity, we only import (`use`) [`cpython`] traits and not
5 //! any of its data objects. We are instead using full qualifiers, such as
5 //! any of its data objects. We are instead using full qualifiers, such as
6 //! `cpython::PyObject`, and believe that the added heaviness is an acceptatble
6 //! `cpython::PyObject`, and believe that the added heaviness is an acceptatble
7 //! price to pay to avoid confusion.
7 //! price to pay to avoid confusion.
8 //!
8 //!
9 //! Also it, is customary in [`cpython`] to label the GIL lifetime as `'p`,
9 //! Also it, is customary in [`cpython`] to label the GIL lifetime as `'p`,
10 //! whereas it is `'py` in PyO3 context. We keep both these conventions in
10 //! whereas it is `'py` in PyO3 context. We keep both these conventions in
11 //! the arguments side of function signatures when they are not simply elided.
11 //! the arguments side of function signatures when they are not simply elided.
12 use pyo3::exceptions::PyTypeError;
12 use pyo3::exceptions::PyTypeError;
13 use pyo3::prelude::*;
13 use pyo3::prelude::*;
14 use pyo3::{pyclass::boolean_struct::False, PyClass};
14
15
15 use cpython::ObjectProtocol;
16 use cpython::ObjectProtocol;
16 use cpython::PythonObject;
17 use cpython::PythonObject;
17 use lazy_static::lazy_static;
18 use lazy_static::lazy_static;
18
19
19 use hg::revlog::index::Index as CoreIndex;
20 use hg::revlog::index::Index as CoreIndex;
20 use rusthg::revlog::{InnerRevlog, PySharedIndex};
21 use rusthg::revlog::{InnerRevlog, PySharedIndex};
21
22
23 /// Marker trait for PyO3 objects with a lifetime representing the acquired GIL
24 ///
25 /// # Safety
26 ///
27 /// This trait must not be implemented for objects with lifetimes that
28 /// do not imply in PyO3 that the GIL is acquired during the whole lifetime.
29 pub unsafe trait WithGIL<'py> {}
30
31 // Safety: the lifetime on these PyO3 objects all represent the acquired GIL
32 unsafe impl<'py> WithGIL<'py> for Python<'py> {}
33 unsafe impl<'py, T> WithGIL<'py> for Bound<'py, T> {}
34 unsafe impl<'py, T: PyClass> WithGIL<'py> for PyRef<'py, T> {}
35 unsafe impl<'py, T: PyClass<Frozen = False>> WithGIL<'py>
36 for PyRefMut<'py, T>
37 {
38 }
39
22 /// Force cpython's GIL handle with the appropriate lifetime
40 /// Force cpython's GIL handle with the appropriate lifetime
23 ///
41 ///
24 /// In `pyo3`, the fact that we have the GIL is expressed by the lifetime of
42 /// In `pyo3`, the fact that we have the GIL is expressed by the lifetime of
25 /// the incoming [`Bound`] smart pointer. We therefore simply instantiate
43 /// the incoming [`Bound`] smart pointer. We therefore simply instantiate
26 /// the `cpython` handle and coerce its lifetime by the function signature.
44 /// the `cpython` handle and coerce its lifetime by the function signature.
27 ///
45 ///
28 /// Reacquiring the GIL is also a possible alternative, as the CPython
46 /// Reacquiring the GIL is also a possible alternative, as the CPython
29 /// documentation explicitely states that "recursive calls are allowed"
47 /// documentation explicitely states that "recursive calls are allowed"
30 /// (we interpret that as saying that acquiring the GIL within a thread that
48 /// (we interpret that as saying that acquiring the GIL within a thread that
31 /// already has it works) *as long as it is properly released*
49 /// already has it works) *as long as it is properly released*
32 /// reference:
50 /// reference:
33 /// <https://docs.python.org/3.8/c-api/init.html#c.PyGILState_Ensure>
51 /// <https://docs.python.org/3.8/c-api/init.html#c.PyGILState_Ensure>
34 pub(crate) fn cpython_handle<'py, T>(
52 pub(crate) fn cpython_handle<'py, T: WithGIL<'py>>(
35 _bound: &Bound<'py, T>,
53 _with_gil: &T,
36 ) -> cpython::Python<'py> {
54 ) -> cpython::Python<'py> {
37 // safety: this is safe because the returned object has the 'py lifetime
55 // safety: this is safe because the returned object has the same lifetime
56 // as the incoming object.
38 unsafe { cpython::Python::assume_gil_acquired() }
57 unsafe { cpython::Python::assume_gil_acquired() }
39 }
58 }
40
59
41 /// Force PyO3 GIL handle from cpython's.
60 /// Force PyO3 GIL handle from cpython's.
42 ///
61 ///
43 /// Very similar to [`cpython_handle`]
62 /// Very similar to [`cpython_handle`]
44 pub fn pyo3_handle(_py: cpython::Python<'_>) -> Python<'_> {
63 pub fn pyo3_handle(_py: cpython::Python<'_>) -> Python<'_> {
45 // safety: this is safe because the returned object has the same lifetime
64 // safety: this is safe because the returned object has the same lifetime
46 // as the incoming object.
65 // as the incoming object.
47 unsafe { Python::assume_gil_acquired() }
66 unsafe { Python::assume_gil_acquired() }
48 }
67 }
49
68
50 /// Convert a PyO3 [`PyObject`] into a [`cpython::PyObject`]
69 /// Convert a PyO3 [`PyObject`] into a [`cpython::PyObject`]
51 ///
70 ///
52 /// During this process, the reference count is increased, then decreased.
71 /// During this process, the reference count is increased, then decreased.
53 /// This means that the GIL (symbolized by the lifetime on the `obj`
72 /// This means that the GIL (symbolized by the lifetime on the `obj`
54 /// argument) is needed.
73 /// argument) is needed.
55 ///
74 ///
56 /// We could make something perhaps more handy by simply stealing the
75 /// We could make something perhaps more handy by simply stealing the
57 /// pointer, forgetting the incoming and then implement `From` with "newtype".
76 /// pointer, forgetting the incoming and then implement `From` with "newtype".
58 /// It would be worth the effort for a generic cpython-to-pyo3 crate, perhaps
77 /// It would be worth the effort for a generic cpython-to-pyo3 crate, perhaps
59 /// not for the current endeavour.
78 /// not for the current endeavour.
60 pub(crate) fn to_cpython_py_object<'py>(
79 pub(crate) fn to_cpython_py_object<'py>(
61 obj: &Bound<'py, PyAny>,
80 obj: &Bound<'py, PyAny>,
62 ) -> (cpython::Python<'py>, cpython::PyObject) {
81 ) -> (cpython::Python<'py>, cpython::PyObject) {
63 let py = cpython_handle(obj);
82 let py = cpython_handle(obj);
64 // public alias of the private cpython::fii::PyObject (!)
83 // public alias of the private cpython::fii::PyObject (!)
65 let raw = obj.as_ptr() as *mut python3_sys::PyObject;
84 let raw = obj.as_ptr() as *mut python3_sys::PyObject;
66 // both pyo3 and rust-cpython will decrement the refcount on drop.
85 // both pyo3 and rust-cpython will decrement the refcount on drop.
67 // If we use from_owned_ptr, that's a segfault.
86 // If we use from_owned_ptr, that's a segfault.
68 (py, unsafe { cpython::PyObject::from_borrowed_ptr(py, raw) })
87 (py, unsafe { cpython::PyObject::from_borrowed_ptr(py, raw) })
69 }
88 }
70
89
71 /// Convert a [`cpython::PyObject`] into a PyO3 [`PyObject`]
90 /// Convert a [`cpython::PyObject`] into a PyO3 [`PyObject`]
72 ///
91 ///
73 /// During this process, the reference count is increased, then decreased.
92 /// During this process, the reference count is increased, then decreased.
74 /// This means that the GIL (symbolized by the PyO3 [`Python`] handle is
93 /// This means that the GIL (symbolized by the PyO3 [`Python`] handle is
75 /// needed.
94 /// needed.
76 ///
95 ///
77 /// We could make something perhaps more handy by simply stealing the
96 /// We could make something perhaps more handy by simply stealing the
78 /// pointer, forgetting the incoming and then implement `From` with "newtype".
97 /// pointer, forgetting the incoming and then implement `From` with "newtype".
79 /// It would be worth the effort for a generic cpython-to-pyo3 crate, perhaps
98 /// It would be worth the effort for a generic cpython-to-pyo3 crate, perhaps
80 /// not for the current endeavour.
99 /// not for the current endeavour.
81 pub(crate) fn from_cpython_py_object(
100 pub(crate) fn from_cpython_py_object(
82 py: Python<'_>,
101 py: Python<'_>,
83 obj: cpython::PyObject,
102 obj: cpython::PyObject,
84 ) -> PyObject {
103 ) -> PyObject {
85 let raw = obj.as_ptr() as *mut pyo3::ffi::PyObject;
104 let raw = obj.as_ptr() as *mut pyo3::ffi::PyObject;
86 unsafe { Py::from_borrowed_ptr(py, raw) }
105 unsafe { Py::from_borrowed_ptr(py, raw) }
87 }
106 }
88
107
89 /// Convert [`cpython::PyErr`] into [`pyo3::PyErr`]
108 /// Convert [`cpython::PyErr`] into [`pyo3::PyErr`]
90 ///
109 ///
91 /// The exception class remains the same as the original exception,
110 /// The exception class remains the same as the original exception,
92 /// hence if it is also defined in another dylib based on `cpython` crate,
111 /// hence if it is also defined in another dylib based on `cpython` crate,
93 /// it will need to be converted to be downcasted in this crate.
112 /// it will need to be converted to be downcasted in this crate.
94 pub(crate) fn from_cpython_pyerr(
113 pub(crate) fn from_cpython_pyerr(
95 py: cpython::Python<'_>,
114 py: cpython::Python<'_>,
96 mut e: cpython::PyErr,
115 mut e: cpython::PyErr,
97 ) -> PyErr {
116 ) -> PyErr {
98 let pyo3_py = pyo3_handle(py);
117 let pyo3_py = pyo3_handle(py);
99 let cpython_exc_obj = e.instance(py);
118 let cpython_exc_obj = e.instance(py);
100 let pyo3_exc_obj = from_cpython_py_object(pyo3_py, cpython_exc_obj);
119 let pyo3_exc_obj = from_cpython_py_object(pyo3_py, cpython_exc_obj);
101 PyErr::from_value(pyo3_exc_obj.into_bound(pyo3_py))
120 PyErr::from_value(pyo3_exc_obj.into_bound(pyo3_py))
102 }
121 }
103
122
104 /// Retrieve the PyType for objects from the `mercurial.rustext` crate.
123 /// Retrieve the PyType for objects from the `mercurial.rustext` crate.
105 fn retrieve_cpython_py_type(
124 fn retrieve_cpython_py_type(
106 submodule_name: &str,
125 submodule_name: &str,
107 type_name: &str,
126 type_name: &str,
108 ) -> cpython::PyResult<cpython::PyType> {
127 ) -> cpython::PyResult<cpython::PyType> {
109 let guard = cpython::Python::acquire_gil();
128 let guard = cpython::Python::acquire_gil();
110 let py = guard.python();
129 let py = guard.python();
111 let module = py.import(&format!("mercurial.rustext.{submodule_name}"))?;
130 let module = py.import(&format!("mercurial.rustext.{submodule_name}"))?;
112 module.get(py, type_name)?.extract::<cpython::PyType>(py)
131 module.get(py, type_name)?.extract::<cpython::PyType>(py)
113 }
132 }
114
133
115 lazy_static! {
134 lazy_static! {
116 static ref INNER_REVLOG_PY_TYPE: cpython::PyType = {
135 static ref INNER_REVLOG_PY_TYPE: cpython::PyType = {
117 retrieve_cpython_py_type("revlog", "InnerRevlog")
136 retrieve_cpython_py_type("revlog", "InnerRevlog")
118 .expect("Could not import InnerRevlog in Python")
137 .expect("Could not import InnerRevlog in Python")
119 };
138 };
120 }
139 }
121
140
122 /// Downcast [`InnerRevlog`], with the appropriate Python type checking.
141 /// Downcast [`InnerRevlog`], with the appropriate Python type checking.
123 ///
142 ///
124 /// The PyType object representing the `InnerRevlog` Python class is not the
143 /// The PyType object representing the `InnerRevlog` Python class is not the
125 /// the same in this dylib as it is in the `mercurial.rustext` module.
144 /// the same in this dylib as it is in the `mercurial.rustext` module.
126 /// This is because the code created with the [`cpython::py_class!`]
145 /// This is because the code created with the [`cpython::py_class!`]
127 /// macro is itself duplicated in both dylibs. In the case of this crate, this
146 /// macro is itself duplicated in both dylibs. In the case of this crate, this
128 /// happens by linking to the [`rusthg`] crate and provides the `InnerRevlog`
147 /// happens by linking to the [`rusthg`] crate and provides the `InnerRevlog`
129 /// that is visible from this crate. The `InnerRevlog::get_type` associated
148 /// that is visible from this crate. The `InnerRevlog::get_type` associated
130 /// function turns out to return a `static mut` (look for `TYPE_OBJECT` in
149 /// function turns out to return a `static mut` (look for `TYPE_OBJECT` in
131 /// `py_class_impl3.rs`), which obviously is different in both dylibs.
150 /// `py_class_impl3.rs`), which obviously is different in both dylibs.
132 ///
151 ///
133 /// The consequence of that is that downcasting an `InnerRevlog` originally
152 /// The consequence of that is that downcasting an `InnerRevlog` originally
134 /// from the `mecurial.rustext` module to our `InnerRevlog` cannot be done with
153 /// from the `mecurial.rustext` module to our `InnerRevlog` cannot be done with
135 /// the usual `extract::<InnerRevlog>(py)`, as it would perform the type
154 /// the usual `extract::<InnerRevlog>(py)`, as it would perform the type
136 /// checking with the `PyType` that is embedded in `mercurial.pyo3_rustext`.
155 /// checking with the `PyType` that is embedded in `mercurial.pyo3_rustext`.
137 /// We must check the `PyType` that is within `mercurial.rustext` instead.
156 /// We must check the `PyType` that is within `mercurial.rustext` instead.
138 /// This is what this function does.
157 /// This is what this function does.
139 fn extract_inner_revlog(
158 fn extract_inner_revlog(
140 py: cpython::Python,
159 py: cpython::Python,
141 inner_revlog: cpython::PyObject,
160 inner_revlog: cpython::PyObject,
142 ) -> PyResult<InnerRevlog> {
161 ) -> PyResult<InnerRevlog> {
143 if !(*INNER_REVLOG_PY_TYPE).is_instance(py, &inner_revlog) {
162 if !(*INNER_REVLOG_PY_TYPE).is_instance(py, &inner_revlog) {
144 return Err(PyTypeError::new_err("Not an InnerRevlog instance"));
163 return Err(PyTypeError::new_err("Not an InnerRevlog instance"));
145 }
164 }
146 // Safety: this is safe because we checked the PyType already, with the
165 // Safety: this is safe because we checked the PyType already, with the
147 // value embedded in `mercurial.rustext`.
166 // value embedded in `mercurial.rustext`.
148 Ok(unsafe { InnerRevlog::unchecked_downcast_from(inner_revlog) })
167 Ok(unsafe { InnerRevlog::unchecked_downcast_from(inner_revlog) })
149 }
168 }
150
169
151 /// This is similar to [`rusthg.py_rust_index_to_graph`], with difference in
170 /// This is similar to [`rusthg.py_rust_index_to_graph`], with difference in
152 /// how we retrieve the [`InnerRevlog`].
171 /// how we retrieve the [`InnerRevlog`].
153 pub fn py_rust_index_to_graph(
172 pub fn py_rust_index_to_graph(
154 py: cpython::Python,
173 py: cpython::Python,
155 index_proxy: cpython::PyObject,
174 index_proxy: cpython::PyObject,
156 ) -> PyResult<cpython::UnsafePyLeaked<PySharedIndex>> {
175 ) -> PyResult<cpython::UnsafePyLeaked<PySharedIndex>> {
157 let inner_revlog = extract_inner_revlog(
176 let inner_revlog = extract_inner_revlog(
158 py,
177 py,
159 index_proxy
178 index_proxy
160 .getattr(py, "inner")
179 .getattr(py, "inner")
161 .map_err(|e| from_cpython_pyerr(py, e))?,
180 .map_err(|e| from_cpython_pyerr(py, e))?,
162 )?;
181 )?;
163
182
164 let leaked = inner_revlog.pub_inner(py).leak_immutable();
183 let leaked = inner_revlog.pub_inner(py).leak_immutable();
165 // Safety: we don't leak the "faked" reference out of the `UnsafePyLeaked`
184 // Safety: we don't leak the "faked" reference out of the `UnsafePyLeaked`
166 Ok(unsafe { leaked.map(py, |idx| PySharedIndex { inner: &idx.index }) })
185 Ok(unsafe { leaked.map(py, |idx| PySharedIndex { inner: &idx.index }) })
167 }
186 }
168
187
169 pub(crate) fn proxy_index_py_leak<'py>(
188 pub(crate) fn proxy_index_py_leak<'py>(
170 index_proxy: &Bound<'py, PyAny>,
189 index_proxy: &Bound<'py, PyAny>,
171 ) -> PyResult<(cpython::Python<'py>, cpython::UnsafePyLeaked<PySharedIndex>)> {
190 ) -> PyResult<(cpython::Python<'py>, cpython::UnsafePyLeaked<PySharedIndex>)> {
172 let (py, idx_proxy) = to_cpython_py_object(index_proxy);
191 let (py, idx_proxy) = to_cpython_py_object(index_proxy);
173 let py_leaked = py_rust_index_to_graph(py, idx_proxy)?;
192 let py_leaked = py_rust_index_to_graph(py, idx_proxy)?;
174 Ok((py, py_leaked))
193 Ok((py, py_leaked))
175 }
194 }
176
195
177 /// Full extraction of the proxy index object as received in PyO3 to a
196 /// Full extraction of the proxy index object as received in PyO3 to a
178 /// [`CoreIndex`] reference.
197 /// [`CoreIndex`] reference.
179 ///
198 ///
180 /// # Safety
199 /// # Safety
181 ///
200 ///
182 /// The invariants to maintain are those of the underlying
201 /// The invariants to maintain are those of the underlying
183 /// [`UnsafePyLeaked::try_borrow`]: the caller must not leak the inner
202 /// [`UnsafePyLeaked::try_borrow`]: the caller must not leak the inner
184 /// reference.
203 /// reference.
185 pub(crate) unsafe fn proxy_index_extract<'py>(
204 pub(crate) unsafe fn proxy_index_extract<'py>(
186 index_proxy: &Bound<'py, PyAny>,
205 index_proxy: &Bound<'py, PyAny>,
187 ) -> PyResult<&'py CoreIndex> {
206 ) -> PyResult<&'py CoreIndex> {
188 let (py, py_leaked) = proxy_index_py_leak(index_proxy)?;
207 let (py, py_leaked) = proxy_index_py_leak(index_proxy)?;
189 let py_shared = &*unsafe {
208 let py_shared = &*unsafe {
190 py_leaked
209 py_leaked
191 .try_borrow(py)
210 .try_borrow(py)
192 .map_err(|e| from_cpython_pyerr(py, e))?
211 .map_err(|e| from_cpython_pyerr(py, e))?
193 };
212 };
194 Ok(py_shared.inner)
213 Ok(py_shared.inner)
195 }
214 }
General Comments 0
You need to be logged in to leave comments. Login now