Show More
@@ -23,15 +23,33 b'' | |||||
23 | //! Macros for use in the `hg-cpython` bridge library. |
|
23 | //! Macros for use in the `hg-cpython` bridge library. | |
24 |
|
24 | |||
25 | use crate::exceptions::AlreadyBorrowed; |
|
25 | use crate::exceptions::AlreadyBorrowed; | |
26 | use cpython::{PyClone, PyObject, PyResult, Python}; |
|
26 | use cpython::{exc, PyClone, PyErr, PyObject, PyResult, Python}; | |
27 | use std::cell::{Cell, Ref, RefCell, RefMut}; |
|
27 | use std::cell::{Cell, Ref, RefCell, RefMut}; | |
28 | use std::ops::{Deref, DerefMut}; |
|
28 | use std::ops::{Deref, DerefMut}; | |
|
29 | use std::sync::atomic::{AtomicUsize, Ordering}; | |||
29 |
|
30 | |||
30 | /// Manages the shared state between Python and Rust |
|
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 | #[derive(Debug, Default)] |
|
44 | #[derive(Debug, Default)] | |
32 | struct PySharedState { |
|
45 | struct PySharedState { | |
33 | leak_count: Cell<usize>, |
|
46 | leak_count: Cell<usize>, | |
34 | mutably_borrowed: Cell<bool>, |
|
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 | // &PySharedState can be Send because any access to inner cells is |
|
55 | // &PySharedState can be Send because any access to inner cells is | |
@@ -54,6 +72,10 b' impl PySharedState {' | |||||
54 | match self.leak_count.get() { |
|
72 | match self.leak_count.get() { | |
55 | 0 => { |
|
73 | 0 => { | |
56 | self.mutably_borrowed.replace(true); |
|
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 | Ok(PyRefMut::new(py, pyrefmut, self)) |
|
79 | Ok(PyRefMut::new(py, pyrefmut, self)) | |
58 | } |
|
80 | } | |
59 | // TODO |
|
81 | // TODO | |
@@ -118,6 +140,10 b' impl PySharedState {' | |||||
118 | self.leak_count.replace(count - 1); |
|
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 | /// `RefCell` wrapper to be safely used in conjunction with `PySharedState`. |
|
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 | /// Manage immutable references to `PyObject` leaked into Python iterators. |
|
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 | pub struct PyLeaked<T> { |
|
340 | pub struct PyLeaked<T> { | |
312 | inner: PyObject, |
|
341 | inner: PyObject, | |
313 | data: Option<T>, |
|
342 | data: Option<T>, | |
314 | py_shared_state: &'static PySharedState, |
|
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 | // DO NOT implement Deref for PyLeaked<T>! Dereferencing PyLeaked |
|
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 | impl<T> PyLeaked<T> { |
|
352 | impl<T> PyLeaked<T> { | |
321 | /// # Safety |
|
353 | /// # Safety | |
@@ -331,14 +363,18 b' impl<T> PyLeaked<T> {' | |||||
331 | inner: inner.clone_ref(py), |
|
363 | inner: inner.clone_ref(py), | |
332 | data: Some(data), |
|
364 | data: Some(data), | |
333 | py_shared_state, |
|
365 | py_shared_state, | |
|
366 | generation: py_shared_state.current_generation(py), | |||
334 | } |
|
367 | } | |
335 | } |
|
368 | } | |
336 |
|
369 | |||
337 | /// Immutably borrows the wrapped value. |
|
370 | /// Immutably borrows the wrapped value. | |
|
371 | /// | |||
|
372 | /// Borrowing fails if the underlying reference has been invalidated. | |||
338 | pub fn try_borrow<'a>( |
|
373 | pub fn try_borrow<'a>( | |
339 | &'a self, |
|
374 | &'a self, | |
340 | py: Python<'a>, |
|
375 | py: Python<'a>, | |
341 | ) -> PyResult<PyLeakedRef<'a, T>> { |
|
376 | ) -> PyResult<PyLeakedRef<'a, T>> { | |
|
377 | self.validate_generation(py)?; | |||
342 | Ok(PyLeakedRef { |
|
378 | Ok(PyLeakedRef { | |
343 | _py: py, |
|
379 | _py: py, | |
344 | data: self.data.as_ref().unwrap(), |
|
380 | data: self.data.as_ref().unwrap(), | |
@@ -347,12 +383,15 b' impl<T> PyLeaked<T> {' | |||||
347 |
|
383 | |||
348 | /// Mutably borrows the wrapped value. |
|
384 | /// Mutably borrows the wrapped value. | |
349 | /// |
|
385 | /// | |
|
386 | /// Borrowing fails if the underlying reference has been invalidated. | |||
|
387 | /// | |||
350 | /// Typically `T` is an iterator. If `T` is an immutable reference, |
|
388 | /// Typically `T` is an iterator. If `T` is an immutable reference, | |
351 | /// `get_mut()` is useless since the inner value can't be mutated. |
|
389 | /// `get_mut()` is useless since the inner value can't be mutated. | |
352 | pub fn try_borrow_mut<'a>( |
|
390 | pub fn try_borrow_mut<'a>( | |
353 | &'a mut self, |
|
391 | &'a mut self, | |
354 | py: Python<'a>, |
|
392 | py: Python<'a>, | |
355 | ) -> PyResult<PyLeakedRefMut<'a, T>> { |
|
393 | ) -> PyResult<PyLeakedRefMut<'a, T>> { | |
|
394 | self.validate_generation(py)?; | |||
356 | Ok(PyLeakedRefMut { |
|
395 | Ok(PyLeakedRefMut { | |
357 | _py: py, |
|
396 | _py: py, | |
358 | data: self.data.as_mut().unwrap(), |
|
397 | data: self.data.as_mut().unwrap(), | |
@@ -364,6 +403,13 b' impl<T> PyLeaked<T> {' | |||||
364 | /// Typically `T` is a static reference to a container, and `U` is an |
|
403 | /// Typically `T` is a static reference to a container, and `U` is an | |
365 | /// iterator of that container. |
|
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 | /// # Safety |
|
413 | /// # Safety | |
368 | /// |
|
414 | /// | |
369 | /// The lifetime of the object passed in to the function `f` is cheated. |
|
415 | /// The lifetime of the object passed in to the function `f` is cheated. | |
@@ -375,6 +421,11 b' impl<T> PyLeaked<T> {' | |||||
375 | py: Python, |
|
421 | py: Python, | |
376 | f: impl FnOnce(T) -> U, |
|
422 | f: impl FnOnce(T) -> U, | |
377 | ) -> PyLeaked<U> { |
|
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 | // f() could make the self.data outlive. That's why map() is unsafe. |
|
429 | // f() could make the self.data outlive. That's why map() is unsafe. | |
379 | // In order to make this function safe, maybe we'll need a way to |
|
430 | // In order to make this function safe, maybe we'll need a way to | |
380 | // temporarily restrict the lifetime of self.data and translate the |
|
431 | // temporarily restrict the lifetime of self.data and translate the | |
@@ -384,6 +435,18 b' impl<T> PyLeaked<T> {' | |||||
384 | inner: self.inner.clone_ref(py), |
|
435 | inner: self.inner.clone_ref(py), | |
385 | data: Some(new_data), |
|
436 | data: Some(new_data), | |
386 | py_shared_state: self.py_shared_state, |
|
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 | #[test] |
|
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 | fn test_borrow_mut_while_leaked() { |
|
683 | fn test_borrow_mut_while_leaked() { | |
586 | let (gil, owner) = prepare_env(); |
|
684 | let (gil, owner) = prepare_env(); | |
587 | let py = gil.python(); |
|
685 | let py = gil.python(); |
General Comments 0
You need to be logged in to leave comments.
Login now