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