rust/hg-cpython/src/ref_sharing.rs
changeset 42768 30320c7bf79f
child 42849 ee0f511b7a22
equal deleted inserted replaced
42767:4b3b27d567d5 42768:30320c7bf79f
       
     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 }