comparison rust/hg-cpython/src/ref_sharing.rs @ 43285:ffc1fbd7d1f5

rust-cpython: make PyLeakedRef operations relatively safe This patch encapsulates the access to the leaked reference to make most leaked-ref operations safe. The only exception is leaked_ref.map(). I couldn't figure out how to allow arbitrary map operation safely over an unsafe static reference. See the docstring and inline comment for details. Now leak_immutable() can be safely implemented as the PyLeakedRef owns its inner data.
author Yuya Nishihara <yuya@tcha.org>
date Sun, 15 Sep 2019 22:19:10 +0900
parents ce6dd1cee4c8
children f8c114f20d2d
comparison
equal deleted inserted replaced
43284:ce6dd1cee4c8 43285:ffc1fbd7d1f5
185 185
186 pub fn borrow_mut(&self) -> PyResult<PyRefMut<'a, T>> { 186 pub fn borrow_mut(&self) -> PyResult<PyRefMut<'a, T>> {
187 self.data.borrow_mut(self.py) 187 self.data.borrow_mut(self.py)
188 } 188 }
189 189
190 /// Returns a leaked reference temporarily held by its management object. 190 /// Returns a leaked reference.
191 /// 191 pub fn leak_immutable(&self) -> PyResult<PyLeakedRef<&'static T>> {
192 /// # Safety 192 let state = &self.data.py_shared_state;
193 /// 193 unsafe {
194 /// It's up to you to make sure that the management object lives 194 let (static_ref, static_state_ref) =
195 /// longer than the leaked reference. Otherwise, you'll get a 195 state.leak_immutable(self.py, self.data)?;
196 /// dangling reference. 196 Ok(PyLeakedRef::new(
197 pub unsafe fn leak_immutable(&self) -> PyResult<PyLeakedRef<&'static T>> { 197 self.py,
198 let (static_ref, static_state_ref) = self 198 self.owner,
199 .data 199 static_ref,
200 .py_shared_state 200 static_state_ref,
201 .leak_immutable(self.py, self.data)?; 201 ))
202 Ok(PyLeakedRef::new( 202 }
203 self.py,
204 self.owner,
205 static_ref,
206 static_state_ref,
207 ))
208 } 203 }
209 } 204 }
210 205
211 /// Holds a mutable reference to data shared between Python and Rust. 206 /// Holds a mutable reference to data shared between Python and Rust.
212 pub struct PyRefMut<'a, T> { 207 pub struct PyRefMut<'a, T> {
314 } 309 }
315 }; 310 };
316 } 311 }
317 312
318 /// Manage immutable references to `PyObject` leaked into Python iterators. 313 /// Manage immutable references to `PyObject` leaked into Python iterators.
319 ///
320 /// In truth, this does not represent leaked references themselves;
321 /// it is instead useful alongside them to manage them.
322 pub struct PyLeakedRef<T> { 314 pub struct PyLeakedRef<T> {
323 _inner: PyObject, 315 inner: PyObject,
324 pub data: Option<T>, // TODO: remove pub 316 data: Option<T>,
325 py_shared_state: &'static PySharedState, 317 py_shared_state: &'static PySharedState,
326 } 318 }
319
320 // DO NOT implement Deref for PyLeakedRef<T>! Dereferencing PyLeakedRef
321 // without taking Python GIL wouldn't be safe.
327 322
328 impl<T> PyLeakedRef<T> { 323 impl<T> PyLeakedRef<T> {
329 /// # Safety 324 /// # Safety
330 /// 325 ///
331 /// The `py_shared_state` must be owned by the `inner` Python object. 326 /// The `py_shared_state` must be owned by the `inner` Python object.
336 inner: &PyObject, 331 inner: &PyObject,
337 data: T, 332 data: T,
338 py_shared_state: &'static PySharedState, 333 py_shared_state: &'static PySharedState,
339 ) -> Self { 334 ) -> Self {
340 Self { 335 Self {
341 _inner: inner.clone_ref(py), 336 inner: inner.clone_ref(py),
342 data: Some(data), 337 data: Some(data),
343 py_shared_state, 338 py_shared_state,
339 }
340 }
341
342 /// Returns an immutable reference to the inner value.
343 pub fn get_ref<'a>(&'a self, _py: Python<'a>) -> &'a T {
344 self.data.as_ref().unwrap()
345 }
346
347 /// Returns a mutable reference to the inner value.
348 ///
349 /// Typically `T` is an iterator. If `T` is an immutable reference,
350 /// `get_mut()` is useless since the inner value can't be mutated.
351 pub fn get_mut<'a>(&'a mut self, _py: Python<'a>) -> &'a mut T {
352 self.data.as_mut().unwrap()
353 }
354
355 /// Converts the inner value by the given function.
356 ///
357 /// Typically `T` is a static reference to a container, and `U` is an
358 /// iterator of that container.
359 ///
360 /// # Safety
361 ///
362 /// The lifetime of the object passed in to the function `f` is cheated.
363 /// It's typically a static reference, but is valid only while the
364 /// corresponding `PyLeakedRef` is alive. Do not copy it out of the
365 /// function call.
366 pub unsafe fn map<U>(
367 mut self,
368 py: Python,
369 f: impl FnOnce(T) -> U,
370 ) -> PyLeakedRef<U> {
371 // f() could make the self.data outlive. That's why map() is unsafe.
372 // In order to make this function safe, maybe we'll need a way to
373 // temporarily restrict the lifetime of self.data and translate the
374 // returned object back to Something<'static>.
375 let new_data = f(self.data.take().unwrap());
376 PyLeakedRef {
377 inner: self.inner.clone_ref(py),
378 data: Some(new_data),
379 py_shared_state: self.py_shared_state,
344 } 380 }
345 } 381 }
346 } 382 }
347 383
348 impl<T> Drop for PyLeakedRef<T> { 384 impl<T> Drop for PyLeakedRef<T> {
350 // py_shared_state should be alive since we do have 386 // py_shared_state should be alive since we do have
351 // a Python reference to the owner object. Taking GIL makes 387 // a Python reference to the owner object. Taking GIL makes
352 // sure that the state is only accessed by this thread. 388 // sure that the state is only accessed by this thread.
353 let gil = Python::acquire_gil(); 389 let gil = Python::acquire_gil();
354 let py = gil.python(); 390 let py = gil.python();
391 if self.data.is_none() {
392 return; // moved to another PyLeakedRef
393 }
355 unsafe { 394 unsafe {
356 self.py_shared_state.decrease_leak_count(py, false); 395 self.py_shared_state.decrease_leak_count(py, false);
357 } 396 }
358 } 397 }
359 } 398 }
381 /// 420 ///
382 /// py_class!(pub class MyType |py| { 421 /// py_class!(pub class MyType |py| {
383 /// data inner: PySharedRefCell<MyStruct>; 422 /// data inner: PySharedRefCell<MyStruct>;
384 /// 423 ///
385 /// def __iter__(&self) -> PyResult<MyTypeItemsIterator> { 424 /// def __iter__(&self) -> PyResult<MyTypeItemsIterator> {
386 /// let mut leak_handle = 425 /// let leaked_ref = self.inner_shared(py).leak_immutable()?;
387 /// unsafe { self.inner_shared(py).leak_immutable()? };
388 /// let leaked_ref = leak_handle.data.take().unwrap();
389 /// MyTypeItemsIterator::from_inner( 426 /// MyTypeItemsIterator::from_inner(
390 /// py, 427 /// py,
391 /// leak_handle, 428 /// unsafe { leaked_ref.map(py, |o| o.iter()) },
392 /// leaked_ref.iter(),
393 /// ) 429 /// )
394 /// } 430 /// }
395 /// }); 431 /// });
396 /// 432 ///
397 /// impl MyType { 433 /// impl MyType {
409 /// 445 ///
410 /// py_shared_ref!(MyType, MyStruct, inner, MyTypeLeakedRef); 446 /// py_shared_ref!(MyType, MyStruct, inner, MyTypeLeakedRef);
411 /// 447 ///
412 /// py_shared_iterator!( 448 /// py_shared_iterator!(
413 /// MyTypeItemsIterator, 449 /// MyTypeItemsIterator,
414 /// PyLeakedRef<&'static MyStruct>, 450 /// PyLeakedRef<HashMap<'static, Vec<u8>, Vec<u8>>>,
415 /// HashMap<'static, Vec<u8>, Vec<u8>>,
416 /// MyType::translate_key_value, 451 /// MyType::translate_key_value,
417 /// Option<(PyBytes, PyBytes)> 452 /// Option<(PyBytes, PyBytes)>
418 /// ); 453 /// );
419 /// ``` 454 /// ```
420 macro_rules! py_shared_iterator { 455 macro_rules! py_shared_iterator {
421 ( 456 (
422 $name: ident, 457 $name: ident,
423 $leaked: ty, 458 $leaked: ty,
424 $iterator_type: ty,
425 $success_func: expr, 459 $success_func: expr,
426 $success_type: ty 460 $success_type: ty
427 ) => { 461 ) => {
428 py_class!(pub class $name |py| { 462 py_class!(pub class $name |py| {
429 data inner: RefCell<Option<$leaked>>; 463 data inner: RefCell<Option<$leaked>>;
430 data it: RefCell<$iterator_type>;
431 464
432 def __next__(&self) -> PyResult<$success_type> { 465 def __next__(&self) -> PyResult<$success_type> {
433 let mut inner_opt = self.inner(py).borrow_mut(); 466 let mut inner_opt = self.inner(py).borrow_mut();
434 if inner_opt.is_some() { 467 if let Some(leaked) = inner_opt.as_mut() {
435 match self.it(py).borrow_mut().next() { 468 match leaked.get_mut(py).next() {
436 None => { 469 None => {
437 // replace Some(inner) by None, drop $leaked 470 // replace Some(inner) by None, drop $leaked
438 inner_opt.take(); 471 inner_opt.take();
439 Ok(None) 472 Ok(None)
440 } 473 }
454 487
455 impl $name { 488 impl $name {
456 pub fn from_inner( 489 pub fn from_inner(
457 py: Python, 490 py: Python,
458 leaked: $leaked, 491 leaked: $leaked,
459 it: $iterator_type
460 ) -> PyResult<Self> { 492 ) -> PyResult<Self> {
461 Self::create_instance( 493 Self::create_instance(
462 py, 494 py,
463 RefCell::new(Some(leaked)), 495 RefCell::new(Some(leaked)),
464 RefCell::new(it)
465 ) 496 )
466 } 497 }
467 } 498 }
468 }; 499 };
469 } 500 }