##// END OF EJS Templates
rust-cpython: add generation counter to leaked reference...
Yuya Nishihara -
r43605:0836efe4 default
parent child Browse files
Show More
@@ -23,15 +23,33 b''
23 23 //! Macros for use in the `hg-cpython` bridge library.
24 24
25 25 use crate::exceptions::AlreadyBorrowed;
26 use cpython::{PyClone, PyObject, PyResult, Python};
26 use cpython::{exc, PyClone, PyErr, PyObject, PyResult, Python};
27 27 use std::cell::{Cell, Ref, RefCell, RefMut};
28 28 use std::ops::{Deref, DerefMut};
29 use std::sync::atomic::{AtomicUsize, Ordering};
29 30
30 31 /// Manages the shared state between Python and Rust
32 ///
33 /// `PySharedState` is owned by `PySharedRefCell`, and is shared across its
34 /// derived references. The consistency of these references are guaranteed
35 /// as follows:
36 ///
37 /// - The immutability of `py_class!` object fields. Any mutation of
38 /// `PySharedRefCell` is allowed only through its `borrow_mut()`.
39 /// - The `py: Python<'_>` token, which makes sure that any data access is
40 /// synchronized by the GIL.
41 /// - The `generation` counter, which increments on `borrow_mut()`. `PyLeaked`
42 /// reference is valid only if the `current_generation()` equals to the
43 /// `generation` at the time of `leak_immutable()`.
31 44 #[derive(Debug, Default)]
32 45 struct PySharedState {
33 46 leak_count: Cell<usize>,
34 47 mutably_borrowed: Cell<bool>,
48 // The counter variable could be Cell<usize> since any operation on
49 // PySharedState is synchronized by the GIL, but being "atomic" makes
50 // PySharedState inherently Sync. The ordering requirement doesn't
51 // matter thanks to the GIL.
52 generation: AtomicUsize,
35 53 }
36 54
37 55 // &PySharedState can be Send because any access to inner cells is
@@ -54,6 +72,10 b' impl PySharedState {'
54 72 match self.leak_count.get() {
55 73 0 => {
56 74 self.mutably_borrowed.replace(true);
75 // Note that this wraps around to the same value if mutably
76 // borrowed more than usize::MAX times, which wouldn't happen
77 // in practice.
78 self.generation.fetch_add(1, Ordering::Relaxed);
57 79 Ok(PyRefMut::new(py, pyrefmut, self))
58 80 }
59 81 // TODO
@@ -118,6 +140,10 b' impl PySharedState {'
118 140 self.leak_count.replace(count - 1);
119 141 }
120 142 }
143
144 fn current_generation(&self, _py: Python) -> usize {
145 self.generation.load(Ordering::Relaxed)
146 }
121 147 }
122 148
123 149 /// `RefCell` wrapper to be safely used in conjunction with `PySharedState`.
@@ -308,14 +334,20 b' macro_rules! py_shared_ref {'
308 334 }
309 335
310 336 /// Manage immutable references to `PyObject` leaked into Python iterators.
337 ///
338 /// This reference will be invalidated once the original value is mutably
339 /// borrowed.
311 340 pub struct PyLeaked<T> {
312 341 inner: PyObject,
313 342 data: Option<T>,
314 343 py_shared_state: &'static PySharedState,
344 /// Generation counter of data `T` captured when PyLeaked is created.
345 generation: usize,
315 346 }
316 347
317 348 // DO NOT implement Deref for PyLeaked<T>! Dereferencing PyLeaked
318 // without taking Python GIL wouldn't be safe.
349 // without taking Python GIL wouldn't be safe. Also, the underling reference
350 // is invalid if generation != py_shared_state.generation.
319 351
320 352 impl<T> PyLeaked<T> {
321 353 /// # Safety
@@ -331,14 +363,18 b' impl<T> PyLeaked<T> {'
331 363 inner: inner.clone_ref(py),
332 364 data: Some(data),
333 365 py_shared_state,
366 generation: py_shared_state.current_generation(py),
334 367 }
335 368 }
336 369
337 370 /// Immutably borrows the wrapped value.
371 ///
372 /// Borrowing fails if the underlying reference has been invalidated.
338 373 pub fn try_borrow<'a>(
339 374 &'a self,
340 375 py: Python<'a>,
341 376 ) -> PyResult<PyLeakedRef<'a, T>> {
377 self.validate_generation(py)?;
342 378 Ok(PyLeakedRef {
343 379 _py: py,
344 380 data: self.data.as_ref().unwrap(),
@@ -347,12 +383,15 b' impl<T> PyLeaked<T> {'
347 383
348 384 /// Mutably borrows the wrapped value.
349 385 ///
386 /// Borrowing fails if the underlying reference has been invalidated.
387 ///
350 388 /// Typically `T` is an iterator. If `T` is an immutable reference,
351 389 /// `get_mut()` is useless since the inner value can't be mutated.
352 390 pub fn try_borrow_mut<'a>(
353 391 &'a mut self,
354 392 py: Python<'a>,
355 393 ) -> PyResult<PyLeakedRefMut<'a, T>> {
394 self.validate_generation(py)?;
356 395 Ok(PyLeakedRefMut {
357 396 _py: py,
358 397 data: self.data.as_mut().unwrap(),
@@ -364,6 +403,13 b' impl<T> PyLeaked<T> {'
364 403 /// Typically `T` is a static reference to a container, and `U` is an
365 404 /// iterator of that container.
366 405 ///
406 /// # Panics
407 ///
408 /// Panics if the underlying reference has been invalidated.
409 ///
410 /// This is typically called immediately after the `PyLeaked` is obtained.
411 /// In which case, the reference must be valid and no panic would occur.
412 ///
367 413 /// # Safety
368 414 ///
369 415 /// The lifetime of the object passed in to the function `f` is cheated.
@@ -375,6 +421,11 b' impl<T> PyLeaked<T> {'
375 421 py: Python,
376 422 f: impl FnOnce(T) -> U,
377 423 ) -> PyLeaked<U> {
424 // Needs to test the generation value to make sure self.data reference
425 // is still intact.
426 self.validate_generation(py)
427 .expect("map() over invalidated leaked reference");
428
378 429 // f() could make the self.data outlive. That's why map() is unsafe.
379 430 // In order to make this function safe, maybe we'll need a way to
380 431 // temporarily restrict the lifetime of self.data and translate the
@@ -384,6 +435,18 b' impl<T> PyLeaked<T> {'
384 435 inner: self.inner.clone_ref(py),
385 436 data: Some(new_data),
386 437 py_shared_state: self.py_shared_state,
438 generation: self.generation,
439 }
440 }
441
442 fn validate_generation(&self, py: Python) -> PyResult<()> {
443 if self.py_shared_state.current_generation(py) == self.generation {
444 Ok(())
445 } else {
446 Err(PyErr::new::<exc::RuntimeError, _>(
447 py,
448 "Cannot access to leaked reference after mutation",
449 ))
387 450 }
388 451 }
389 452 }
@@ -582,6 +645,41 b' mod test {'
582 645 }
583 646
584 647 #[test]
648 fn test_leaked_borrow_after_mut() {
649 let (gil, owner) = prepare_env();
650 let py = gil.python();
651 let leaked = owner.string_shared(py).leak_immutable().unwrap();
652 owner.string(py).py_shared_state.leak_count.replace(0); // XXX cheat
653 owner.string_shared(py).borrow_mut().unwrap().clear();
654 owner.string(py).py_shared_state.leak_count.replace(1); // XXX cheat
655 assert!(leaked.try_borrow(py).is_err());
656 }
657
658 #[test]
659 fn test_leaked_borrow_mut_after_mut() {
660 let (gil, owner) = prepare_env();
661 let py = gil.python();
662 let leaked = owner.string_shared(py).leak_immutable().unwrap();
663 let mut leaked_iter = unsafe { leaked.map(py, |s| s.chars()) };
664 owner.string(py).py_shared_state.leak_count.replace(0); // XXX cheat
665 owner.string_shared(py).borrow_mut().unwrap().clear();
666 owner.string(py).py_shared_state.leak_count.replace(1); // XXX cheat
667 assert!(leaked_iter.try_borrow_mut(py).is_err());
668 }
669
670 #[test]
671 #[should_panic(expected = "map() over invalidated leaked reference")]
672 fn test_leaked_map_after_mut() {
673 let (gil, owner) = prepare_env();
674 let py = gil.python();
675 let leaked = owner.string_shared(py).leak_immutable().unwrap();
676 owner.string(py).py_shared_state.leak_count.replace(0); // XXX cheat
677 owner.string_shared(py).borrow_mut().unwrap().clear();
678 owner.string(py).py_shared_state.leak_count.replace(1); // XXX cheat
679 let _leaked_iter = unsafe { leaked.map(py, |s| s.chars()) };
680 }
681
682 #[test]
585 683 fn test_borrow_mut_while_leaked() {
586 684 let (gil, owner) = prepare_env();
587 685 let py = gil.python();
General Comments 0
You need to be logged in to leave comments. Login now