##// END OF EJS Templates
rust-cpython: add macro for sharing references...
Raphaël Gomès -
r42979:826407e1 default draft
parent child Browse files
Show More
@@ -0,0 +1,375 b''
1 // macros.rs
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 //
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
7
8 //! Macros for use in the `hg-cpython` bridge library.
9
10 use crate::exceptions::AlreadyBorrowed;
11 use cpython::{PyResult, Python};
12 use std::cell::{Cell, RefCell, RefMut};
13
14 /// Manages the shared state between Python and Rust
15 #[derive(Default)]
16 pub struct PySharedState {
17 leak_count: Cell<usize>,
18 mutably_borrowed: Cell<bool>,
19 }
20
21 impl PySharedState {
22 pub fn borrow_mut<'a, T>(
23 &'a self,
24 py: Python<'a>,
25 pyrefmut: RefMut<'a, T>,
26 ) -> PyResult<PyRefMut<'a, T>> {
27 if self.mutably_borrowed.get() {
28 return Err(AlreadyBorrowed::new(
29 py,
30 "Cannot borrow mutably while there exists another \
31 mutable reference in a Python object",
32 ));
33 }
34 match self.leak_count.get() {
35 0 => {
36 self.mutably_borrowed.replace(true);
37 Ok(PyRefMut::new(py, pyrefmut, self))
38 }
39 // TODO
40 // For now, this works differently than Python references
41 // in the case of iterators.
42 // Python does not complain when the data an iterator
43 // points to is modified if the iterator is never used
44 // afterwards.
45 // Here, we are stricter than this by refusing to give a
46 // mutable reference if it is already borrowed.
47 // While the additional safety might be argued for, it
48 // breaks valid programming patterns in Python and we need
49 // to fix this issue down the line.
50 _ => Err(AlreadyBorrowed::new(
51 py,
52 "Cannot borrow mutably while there are \
53 immutable references in Python objects",
54 )),
55 }
56 }
57
58 /// Return a reference to the wrapped data with an artificial static
59 /// lifetime.
60 /// We need to be protected by the GIL for thread-safety.
61 pub fn leak_immutable<T>(
62 &self,
63 py: Python,
64 data: &RefCell<T>,
65 ) -> PyResult<&'static T> {
66 if self.mutably_borrowed.get() {
67 return Err(AlreadyBorrowed::new(
68 py,
69 "Cannot borrow immutably while there is a \
70 mutable reference in Python objects",
71 ));
72 }
73 let ptr = data.as_ptr();
74 self.leak_count.replace(self.leak_count.get() + 1);
75 unsafe { Ok(&*ptr) }
76 }
77
78 pub fn decrease_leak_count(&self, _py: Python, mutable: bool) {
79 self.leak_count
80 .replace(self.leak_count.get().saturating_sub(1));
81 if mutable {
82 self.mutably_borrowed.replace(false);
83 }
84 }
85 }
86
87 /// Holds a mutable reference to data shared between Python and Rust.
88 pub struct PyRefMut<'a, T> {
89 inner: RefMut<'a, T>,
90 py_shared_state: &'a PySharedState,
91 }
92
93 impl<'a, T> PyRefMut<'a, T> {
94 fn new(
95 _py: Python<'a>,
96 inner: RefMut<'a, T>,
97 py_shared_state: &'a PySharedState,
98 ) -> Self {
99 Self {
100 inner,
101 py_shared_state,
102 }
103 }
104 }
105
106 impl<'a, T> std::ops::Deref for PyRefMut<'a, T> {
107 type Target = RefMut<'a, T>;
108
109 fn deref(&self) -> &Self::Target {
110 &self.inner
111 }
112 }
113 impl<'a, T> std::ops::DerefMut for PyRefMut<'a, T> {
114 fn deref_mut(&mut self) -> &mut Self::Target {
115 &mut self.inner
116 }
117 }
118
119 impl<'a, T> Drop for PyRefMut<'a, T> {
120 fn drop(&mut self) {
121 let gil = Python::acquire_gil();
122 let py = gil.python();
123 self.py_shared_state.decrease_leak_count(py, true);
124 }
125 }
126
127 /// Allows a `py_class!` generated struct to share references to one of its
128 /// data members with Python.
129 ///
130 /// # Warning
131 ///
132 /// The targeted `py_class!` needs to have the
133 /// `data py_shared_state: PySharedState;` data attribute to compile.
134 /// A better, more complicated macro is needed to automatically insert it,
135 /// but this one is not yet really battle tested (what happens when
136 /// multiple references are needed?). See the example below.
137 ///
138 /// TODO allow Python container types: for now, integration with the garbage
139 /// collector does not extend to Rust structs holding references to Python
140 /// objects. Should the need surface, `__traverse__` and `__clear__` will
141 /// need to be written as per the `rust-cpython` docs on GC integration.
142 ///
143 /// # Parameters
144 ///
145 /// * `$name` is the same identifier used in for `py_class!` macro call.
146 /// * `$inner_struct` is the identifier of the underlying Rust struct
147 /// * `$data_member` is the identifier of the data member of `$inner_struct`
148 /// that will be shared.
149 /// * `$leaked` is the identifier to give to the struct that will manage
150 /// references to `$name`, to be used for example in other macros like
151 /// `py_shared_mapping_iterator`.
152 ///
153 /// # Example
154 ///
155 /// ```
156 /// struct MyStruct {
157 /// inner: Vec<u32>;
158 /// }
159 ///
160 /// py_class!(pub class MyType |py| {
161 /// data inner: RefCell<MyStruct>;
162 /// data py_shared_state: PySharedState;
163 /// });
164 ///
165 /// py_shared_ref!(MyType, MyStruct, inner, MyTypeLeakedRef);
166 /// ```
167 macro_rules! py_shared_ref {
168 (
169 $name: ident,
170 $inner_struct: ident,
171 $data_member: ident,
172 $leaked: ident,
173 ) => {
174 impl $name {
175 fn borrow_mut<'a>(
176 &'a self,
177 py: Python<'a>,
178 ) -> PyResult<crate::ref_sharing::PyRefMut<'a, $inner_struct>>
179 {
180 self.py_shared_state(py)
181 .borrow_mut(py, self.$data_member(py).borrow_mut())
182 }
183
184 fn leak_immutable<'a>(
185 &'a self,
186 py: Python<'a>,
187 ) -> PyResult<&'static $inner_struct> {
188 self.py_shared_state(py)
189 .leak_immutable(py, self.$data_member(py))
190 }
191 }
192
193 /// Manage immutable references to `$name` leaked into Python
194 /// iterators.
195 ///
196 /// In truth, this does not represent leaked references themselves;
197 /// it is instead useful alongside them to manage them.
198 pub struct $leaked {
199 inner: $name,
200 }
201
202 impl $leaked {
203 fn new(py: Python, inner: &$name) -> Self {
204 Self {
205 inner: inner.clone_ref(py),
206 }
207 }
208 }
209
210 impl Drop for $leaked {
211 fn drop(&mut self) {
212 let gil = Python::acquire_gil();
213 let py = gil.python();
214 self.inner
215 .py_shared_state(py)
216 .decrease_leak_count(py, false);
217 }
218 }
219 };
220 }
221
222 /// Defines a `py_class!` that acts as a Python iterator over a Rust iterator.
223 macro_rules! py_shared_iterator_impl {
224 (
225 $name: ident,
226 $leaked: ident,
227 $iterator_type: ty,
228 $success_func: expr,
229 $success_type: ty
230 ) => {
231 py_class!(pub class $name |py| {
232 data inner: RefCell<Option<$leaked>>;
233 data it: RefCell<$iterator_type>;
234
235 def __next__(&self) -> PyResult<$success_type> {
236 let mut inner_opt = self.inner(py).borrow_mut();
237 if inner_opt.is_some() {
238 match self.it(py).borrow_mut().next() {
239 None => {
240 // replace Some(inner) by None, drop $leaked
241 inner_opt.take();
242 Ok(None)
243 }
244 Some(res) => {
245 $success_func(py, res)
246 }
247 }
248 } else {
249 Ok(None)
250 }
251 }
252
253 def __iter__(&self) -> PyResult<Self> {
254 Ok(self.clone_ref(py))
255 }
256 });
257
258 impl $name {
259 pub fn from_inner(
260 py: Python,
261 leaked: Option<$leaked>,
262 it: $iterator_type
263 ) -> PyResult<Self> {
264 Self::create_instance(
265 py,
266 RefCell::new(leaked),
267 RefCell::new(it)
268 )
269 }
270 }
271 };
272 }
273
274 /// Defines a `py_class!` that acts as a Python mapping iterator over a Rust
275 /// iterator.
276 ///
277 /// TODO: this is a bit awkward to use, and a better (more complicated)
278 /// procedural macro would simplify the interface a lot.
279 ///
280 /// # Parameters
281 ///
282 /// * `$name` is the identifier to give to the resulting Rust struct.
283 /// * `$leaked` corresponds to `$leaked` in the matching `py_shared_ref!` call.
284 /// * `$key_type` is the type of the key in the mapping
285 /// * `$value_type` is the type of the value in the mapping
286 /// * `$success_func` is a function for processing the Rust `(key, value)`
287 /// tuple on iteration success, turning it into something Python understands.
288 /// * `$success_func` is the return type of `$success_func`
289 ///
290 /// # Example
291 ///
292 /// ```
293 /// struct MyStruct {
294 /// inner: HashMap<Vec<u8>, Vec<u8>>;
295 /// }
296 ///
297 /// py_class!(pub class MyType |py| {
298 /// data inner: RefCell<MyStruct>;
299 /// data py_shared_state: PySharedState;
300 ///
301 /// def __iter__(&self) -> PyResult<MyTypeItemsIterator> {
302 /// MyTypeItemsIterator::create_instance(
303 /// py,
304 /// RefCell::new(Some(MyTypeLeakedRef::new(py, &self))),
305 /// RefCell::new(self.leak_immutable(py).iter()),
306 /// )
307 /// }
308 /// });
309 ///
310 /// impl MyType {
311 /// fn translate_key_value(
312 /// py: Python,
313 /// res: (&Vec<u8>, &Vec<u8>),
314 /// ) -> PyResult<Option<(PyBytes, PyBytes)>> {
315 /// let (f, entry) = res;
316 /// Ok(Some((
317 /// PyBytes::new(py, f),
318 /// PyBytes::new(py, entry),
319 /// )))
320 /// }
321 /// }
322 ///
323 /// py_shared_ref!(MyType, MyStruct, inner, MyTypeLeakedRef);
324 ///
325 /// py_shared_mapping_iterator!(
326 /// MyTypeItemsIterator,
327 /// MyTypeLeakedRef,
328 /// Vec<u8>,
329 /// Vec<u8>,
330 /// MyType::translate_key_value,
331 /// Option<(PyBytes, PyBytes)>
332 /// );
333 /// ```
334 #[allow(unused)] // Removed in a future patch
335 macro_rules! py_shared_mapping_iterator {
336 (
337 $name:ident,
338 $leaked:ident,
339 $key_type: ty,
340 $value_type: ty,
341 $success_func: path,
342 $success_type: ty
343 ) => {
344 py_shared_iterator_impl!(
345 $name,
346 $leaked,
347 Box<
348 Iterator<Item = (&'static $key_type, &'static $value_type)>
349 + Send,
350 >,
351 $success_func,
352 $success_type
353 );
354 };
355 }
356
357 /// Works basically the same as `py_shared_mapping_iterator`, but with only a
358 /// key.
359 macro_rules! py_shared_sequence_iterator {
360 (
361 $name:ident,
362 $leaked:ident,
363 $key_type: ty,
364 $success_func: path,
365 $success_type: ty
366 ) => {
367 py_shared_iterator_impl!(
368 $name,
369 $leaked,
370 Box<Iterator<Item = &'static $key_type> + Send>,
371 $success_func,
372 $success_type
373 );
374 };
375 }
@@ -9,21 +9,22 b''
9 //! `hg-core` package.
9 //! `hg-core` package.
10
10
11 use std::cell::RefCell;
11 use std::cell::RefCell;
12 use std::convert::TryInto;
12
13
13 use cpython::{
14 use cpython::{
14 exc, ObjectProtocol, PyBytes, PyDict, PyErr, PyObject, PyResult,
15 exc, ObjectProtocol, PyBytes, PyClone, PyDict, PyErr, PyObject, PyResult,
15 PythonObject, ToPyObject,
16 Python,
16 };
17 };
17
18
18 use crate::dirstate::extract_dirstate;
19 use crate::{dirstate::extract_dirstate, ref_sharing::PySharedState};
19 use hg::{
20 use hg::{
20 DirsIterable, DirsMultiset, DirstateMapError, DirstateParseError,
21 DirsIterable, DirsMultiset, DirstateMapError, DirstateParseError,
21 EntryState,
22 EntryState,
22 };
23 };
23 use std::convert::TryInto;
24
24
25 py_class!(pub class Dirs |py| {
25 py_class!(pub class Dirs |py| {
26 data dirs_map: RefCell<DirsMultiset>;
26 data inner: RefCell<DirsMultiset>;
27 data py_shared_state: PySharedState;
27
28
28 // `map` is either a `dict` or a flat iterator (usually a `set`, sometimes
29 // `map` is either a `dict` or a flat iterator (usually a `set`, sometimes
29 // a `list`)
30 // a `list`)
@@ -59,18 +60,22 b' py_class!(pub class Dirs |py| {'
59 )
60 )
60 };
61 };
61
62
62 Self::create_instance(py, RefCell::new(inner))
63 Self::create_instance(
64 py,
65 RefCell::new(inner),
66 PySharedState::default()
67 )
63 }
68 }
64
69
65 def addpath(&self, path: PyObject) -> PyResult<PyObject> {
70 def addpath(&self, path: PyObject) -> PyResult<PyObject> {
66 self.dirs_map(py).borrow_mut().add_path(
71 self.borrow_mut(py)?.add_path(
67 path.extract::<PyBytes>(py)?.data(py),
72 path.extract::<PyBytes>(py)?.data(py),
68 );
73 );
69 Ok(py.None())
74 Ok(py.None())
70 }
75 }
71
76
72 def delpath(&self, path: PyObject) -> PyResult<PyObject> {
77 def delpath(&self, path: PyObject) -> PyResult<PyObject> {
73 self.dirs_map(py).borrow_mut().delete_path(
78 self.borrow_mut(py)?.delete_path(
74 path.extract::<PyBytes>(py)?.data(py),
79 path.extract::<PyBytes>(py)?.data(py),
75 )
80 )
76 .and(Ok(py.None()))
81 .and(Ok(py.None()))
@@ -88,26 +93,38 b' py_class!(pub class Dirs |py| {'
88 }
93 }
89 })
94 })
90 }
95 }
91
96 def __iter__(&self) -> PyResult<DirsMultisetKeysIterator> {
92 // This is really inefficient on top of being ugly, but it's an easy way
97 DirsMultisetKeysIterator::create_instance(
93 // of having it work to continue working on the rest of the module
98 py,
94 // hopefully bypassing Python entirely pretty soon.
99 RefCell::new(Some(DirsMultisetLeakedRef::new(py, &self))),
95 def __iter__(&self) -> PyResult<PyObject> {
100 RefCell::new(Box::new(self.leak_immutable(py)?.iter())),
96 let dirs = self.dirs_map(py).borrow();
101 )
97 let dirs: Vec<_> = dirs
98 .iter()
99 .map(|d| PyBytes::new(py, d))
100 .collect();
101 dirs.to_py_object(py)
102 .into_object()
103 .iter(py)
104 .map(|o| o.into_object())
105 }
102 }
106
103
107 def __contains__(&self, item: PyObject) -> PyResult<bool> {
104 def __contains__(&self, item: PyObject) -> PyResult<bool> {
108 Ok(self
105 Ok(self
109 .dirs_map(py)
106 .inner(py)
110 .borrow()
107 .borrow()
111 .contains(item.extract::<PyBytes>(py)?.data(py).as_ref()))
108 .contains(item.extract::<PyBytes>(py)?.data(py).as_ref()))
112 }
109 }
113 });
110 });
111
112 py_shared_ref!(Dirs, DirsMultiset, inner, DirsMultisetLeakedRef,);
113
114 impl Dirs {
115 pub fn from_inner(py: Python, d: DirsMultiset) -> PyResult<Self> {
116 Self::create_instance(py, RefCell::new(d), PySharedState::default())
117 }
118
119 fn translate_key(py: Python, res: &Vec<u8>) -> PyResult<Option<PyBytes>> {
120 Ok(Some(PyBytes::new(py, res)))
121 }
122 }
123
124 py_shared_sequence_iterator!(
125 DirsMultisetKeysIterator,
126 DirsMultisetLeakedRef,
127 Vec<u8>,
128 Dirs::translate_key,
129 Option<PyBytes>
130 );
@@ -67,3 +67,5 b' impl PatternFileError {'
67 }
67 }
68 }
68 }
69 }
69 }
70
71 py_exception!(shared_ref, AlreadyBorrowed, RuntimeError);
@@ -27,6 +27,8 b' extern crate cpython;'
27 pub mod ancestors;
27 pub mod ancestors;
28 mod cindex;
28 mod cindex;
29 mod conversion;
29 mod conversion;
30 #[macro_use]
31 pub mod ref_sharing;
30 pub mod dagops;
32 pub mod dagops;
31 pub mod dirstate;
33 pub mod dirstate;
32 pub mod parsers;
34 pub mod parsers;
General Comments 0
You need to be logged in to leave comments. Login now