comparison rust/hg-cpython/src/ref_sharing.rs @ 43425:ed50f2c31a4c

rust-cpython: allow mutation unless leaked reference is borrowed In other words, mutation is allowed while a Python iterator holding PyLeaked exists. The iterator will be invalidated instead. We still need a borrow_count to prevent mutation while leaked data is dereferenced in Rust world, but most leak_count business is superseded by the generation counter. decrease_leak_count(py, true) will be removed soon.
author Yuya Nishihara <yuya@tcha.org>
date Sat, 12 Oct 2019 20:26:38 +0900
parents 0836efe4967b
children 6f9f15a476a4
comparison
equal deleted inserted replaced
43424:0836efe4967b 43425:ed50f2c31a4c
36 /// 36 ///
37 /// - The immutability of `py_class!` object fields. Any mutation of 37 /// - The immutability of `py_class!` object fields. Any mutation of
38 /// `PySharedRefCell` is allowed only through its `borrow_mut()`. 38 /// `PySharedRefCell` is allowed only through its `borrow_mut()`.
39 /// - The `py: Python<'_>` token, which makes sure that any data access is 39 /// - The `py: Python<'_>` token, which makes sure that any data access is
40 /// synchronized by the GIL. 40 /// synchronized by the GIL.
41 /// - The `borrow_count`, which is the number of references borrowed from
42 /// `PyLeaked`. Just like `RefCell`, mutation is prohibited while `PyLeaked`
43 /// is borrowed.
41 /// - The `generation` counter, which increments on `borrow_mut()`. `PyLeaked` 44 /// - The `generation` counter, which increments on `borrow_mut()`. `PyLeaked`
42 /// reference is valid only if the `current_generation()` equals to the 45 /// reference is valid only if the `current_generation()` equals to the
43 /// `generation` at the time of `leak_immutable()`. 46 /// `generation` at the time of `leak_immutable()`.
44 #[derive(Debug, Default)] 47 #[derive(Debug, Default)]
45 struct PySharedState { 48 struct PySharedState {
46 leak_count: Cell<usize>,
47 mutably_borrowed: Cell<bool>, 49 mutably_borrowed: Cell<bool>,
48 // The counter variable could be Cell<usize> since any operation on 50 // The counter variable could be Cell<usize> since any operation on
49 // PySharedState is synchronized by the GIL, but being "atomic" makes 51 // PySharedState is synchronized by the GIL, but being "atomic" makes
50 // PySharedState inherently Sync. The ordering requirement doesn't 52 // PySharedState inherently Sync. The ordering requirement doesn't
51 // matter thanks to the GIL. 53 // matter thanks to the GIL.
54 borrow_count: AtomicUsize,
52 generation: AtomicUsize, 55 generation: AtomicUsize,
53 } 56 }
54 57
55 // &PySharedState can be Send because any access to inner cells is 58 // &PySharedState can be Send because any access to inner cells is
56 // synchronized by the GIL. 59 // synchronized by the GIL.
67 py, 70 py,
68 "Cannot borrow mutably while there exists another \ 71 "Cannot borrow mutably while there exists another \
69 mutable reference in a Python object", 72 mutable reference in a Python object",
70 )); 73 ));
71 } 74 }
72 match self.leak_count.get() { 75 match self.current_borrow_count(py) {
73 0 => { 76 0 => {
74 self.mutably_borrowed.replace(true); 77 self.mutably_borrowed.replace(true);
75 // Note that this wraps around to the same value if mutably 78 // Note that this wraps around to the same value if mutably
76 // borrowed more than usize::MAX times, which wouldn't happen 79 // borrowed more than usize::MAX times, which wouldn't happen
77 // in practice. 80 // in practice.
78 self.generation.fetch_add(1, Ordering::Relaxed); 81 self.generation.fetch_add(1, Ordering::Relaxed);
79 Ok(PyRefMut::new(py, pyrefmut, self)) 82 Ok(PyRefMut::new(py, pyrefmut, self))
80 } 83 }
81 // TODO
82 // For now, this works differently than Python references
83 // in the case of iterators.
84 // Python does not complain when the data an iterator
85 // points to is modified if the iterator is never used
86 // afterwards.
87 // Here, we are stricter than this by refusing to give a
88 // mutable reference if it is already borrowed.
89 // While the additional safety might be argued for, it
90 // breaks valid programming patterns in Python and we need
91 // to fix this issue down the line.
92 _ => Err(AlreadyBorrowed::new( 84 _ => Err(AlreadyBorrowed::new(
93 py, 85 py,
94 "Cannot borrow mutably while there are \ 86 "Cannot borrow mutably while immutably borrowed",
95 immutable references in Python objects",
96 )), 87 )),
97 } 88 }
98 } 89 }
99 90
100 /// Return a reference to the wrapped data and its state with an 91 /// Return a reference to the wrapped data and its state with an
119 } 110 }
120 // TODO: it's weird that self is data.py_shared_state. Maybe we 111 // TODO: it's weird that self is data.py_shared_state. Maybe we
121 // can move stuff to PySharedRefCell? 112 // can move stuff to PySharedRefCell?
122 let ptr = data.as_ptr(); 113 let ptr = data.as_ptr();
123 let state_ptr: *const PySharedState = &data.py_shared_state; 114 let state_ptr: *const PySharedState = &data.py_shared_state;
124 self.leak_count.replace(self.leak_count.get() + 1);
125 Ok((&*ptr, &*state_ptr)) 115 Ok((&*ptr, &*state_ptr))
116 }
117
118 fn current_borrow_count(&self, _py: Python) -> usize {
119 self.borrow_count.load(Ordering::Relaxed)
120 }
121
122 fn increase_borrow_count(&self, _py: Python) {
123 // Note that this wraps around if there are more than usize::MAX
124 // borrowed references, which shouldn't happen due to memory limit.
125 self.borrow_count.fetch_add(1, Ordering::Relaxed);
126 }
127
128 fn decrease_borrow_count(&self, _py: Python) {
129 let prev_count = self.borrow_count.fetch_sub(1, Ordering::Relaxed);
130 assert!(prev_count > 0);
126 } 131 }
127 132
128 /// # Safety 133 /// # Safety
129 /// 134 ///
130 /// It's up to you to make sure the reference is about to be deleted 135 /// It's up to you to make sure the reference is about to be deleted
131 /// when updating the leak count. 136 /// when updating the leak count.
132 fn decrease_leak_count(&self, _py: Python, mutable: bool) { 137 fn decrease_leak_count(&self, py: Python, mutable: bool) {
133 if mutable { 138 if mutable {
134 assert_eq!(self.leak_count.get(), 0); 139 assert_eq!(self.current_borrow_count(py), 0);
135 assert!(self.mutably_borrowed.get()); 140 assert!(self.mutably_borrowed.get());
136 self.mutably_borrowed.replace(false); 141 self.mutably_borrowed.replace(false);
137 } else { 142 } else {
138 let count = self.leak_count.get(); 143 unimplemented!();
139 assert!(count > 0);
140 self.leak_count.replace(count - 1);
141 } 144 }
142 } 145 }
143 146
144 fn current_generation(&self, _py: Python) -> usize { 147 fn current_generation(&self, _py: Python) -> usize {
145 self.generation.load(Ordering::Relaxed) 148 self.generation.load(Ordering::Relaxed)
149 }
150 }
151
152 /// Helper to keep the borrow count updated while the shared object is
153 /// immutably borrowed without using the `RefCell` interface.
154 struct BorrowPyShared<'a> {
155 py: Python<'a>,
156 py_shared_state: &'a PySharedState,
157 }
158
159 impl<'a> BorrowPyShared<'a> {
160 fn new(
161 py: Python<'a>,
162 py_shared_state: &'a PySharedState,
163 ) -> BorrowPyShared<'a> {
164 py_shared_state.increase_borrow_count(py);
165 BorrowPyShared {
166 py,
167 py_shared_state,
168 }
169 }
170 }
171
172 impl Drop for BorrowPyShared<'_> {
173 fn drop(&mut self) {
174 self.py_shared_state.decrease_borrow_count(self.py);
146 } 175 }
147 } 176 }
148 177
149 /// `RefCell` wrapper to be safely used in conjunction with `PySharedState`. 178 /// `RefCell` wrapper to be safely used in conjunction with `PySharedState`.
150 /// 179 ///
270 } 299 }
271 } 300 }
272 301
273 /// Allows a `py_class!` generated struct to share references to one of its 302 /// Allows a `py_class!` generated struct to share references to one of its
274 /// data members with Python. 303 /// data members with Python.
275 ///
276 /// # Warning
277 ///
278 /// TODO allow Python container types: for now, integration with the garbage
279 /// collector does not extend to Rust structs holding references to Python
280 /// objects. Should the need surface, `__traverse__` and `__clear__` will
281 /// need to be written as per the `rust-cpython` docs on GC integration.
282 /// 304 ///
283 /// # Parameters 305 /// # Parameters
284 /// 306 ///
285 /// * `$name` is the same identifier used in for `py_class!` macro call. 307 /// * `$name` is the same identifier used in for `py_class!` macro call.
286 /// * `$inner_struct` is the identifier of the underlying Rust struct 308 /// * `$inner_struct` is the identifier of the underlying Rust struct
374 &'a self, 396 &'a self,
375 py: Python<'a>, 397 py: Python<'a>,
376 ) -> PyResult<PyLeakedRef<'a, T>> { 398 ) -> PyResult<PyLeakedRef<'a, T>> {
377 self.validate_generation(py)?; 399 self.validate_generation(py)?;
378 Ok(PyLeakedRef { 400 Ok(PyLeakedRef {
379 _py: py, 401 _borrow: BorrowPyShared::new(py, self.py_shared_state),
380 data: self.data.as_ref().unwrap(), 402 data: self.data.as_ref().unwrap(),
381 }) 403 })
382 } 404 }
383 405
384 /// Mutably borrows the wrapped value. 406 /// Mutably borrows the wrapped value.
391 &'a mut self, 413 &'a mut self,
392 py: Python<'a>, 414 py: Python<'a>,
393 ) -> PyResult<PyLeakedRefMut<'a, T>> { 415 ) -> PyResult<PyLeakedRefMut<'a, T>> {
394 self.validate_generation(py)?; 416 self.validate_generation(py)?;
395 Ok(PyLeakedRefMut { 417 Ok(PyLeakedRefMut {
396 _py: py, 418 _borrow: BorrowPyShared::new(py, self.py_shared_state),
397 data: self.data.as_mut().unwrap(), 419 data: self.data.as_mut().unwrap(),
398 }) 420 })
399 } 421 }
400 422
401 /// Converts the inner value by the given function. 423 /// Converts the inner value by the given function.
449 )) 471 ))
450 } 472 }
451 } 473 }
452 } 474 }
453 475
454 impl<T> Drop for PyLeaked<T> {
455 fn drop(&mut self) {
456 // py_shared_state should be alive since we do have
457 // a Python reference to the owner object. Taking GIL makes
458 // sure that the state is only accessed by this thread.
459 let gil = Python::acquire_gil();
460 let py = gil.python();
461 if self.data.is_none() {
462 return; // moved to another PyLeaked
463 }
464 self.py_shared_state.decrease_leak_count(py, false);
465 }
466 }
467
468 /// Immutably borrowed reference to a leaked value. 476 /// Immutably borrowed reference to a leaked value.
469 pub struct PyLeakedRef<'a, T> { 477 pub struct PyLeakedRef<'a, T> {
470 _py: Python<'a>, 478 _borrow: BorrowPyShared<'a>,
471 data: &'a T, 479 data: &'a T,
472 } 480 }
473 481
474 impl<T> Deref for PyLeakedRef<'_, T> { 482 impl<T> Deref for PyLeakedRef<'_, T> {
475 type Target = T; 483 type Target = T;
479 } 487 }
480 } 488 }
481 489
482 /// Mutably borrowed reference to a leaked value. 490 /// Mutably borrowed reference to a leaked value.
483 pub struct PyLeakedRefMut<'a, T> { 491 pub struct PyLeakedRefMut<'a, T> {
484 _py: Python<'a>, 492 _borrow: BorrowPyShared<'a>,
485 data: &'a mut T, 493 data: &'a mut T,
486 } 494 }
487 495
488 impl<T> Deref for PyLeakedRefMut<'_, T> { 496 impl<T> Deref for PyLeakedRefMut<'_, T> {
489 type Target = T; 497 type Target = T;
568 let mut inner_opt = self.inner(py).borrow_mut(); 576 let mut inner_opt = self.inner(py).borrow_mut();
569 if let Some(leaked) = inner_opt.as_mut() { 577 if let Some(leaked) = inner_opt.as_mut() {
570 let mut iter = leaked.try_borrow_mut(py)?; 578 let mut iter = leaked.try_borrow_mut(py)?;
571 match iter.next() { 579 match iter.next() {
572 None => { 580 None => {
581 drop(iter);
573 // replace Some(inner) by None, drop $leaked 582 // replace Some(inner) by None, drop $leaked
574 inner_opt.take(); 583 inner_opt.take();
575 Ok(None) 584 Ok(None)
576 } 585 }
577 Some(res) => { 586 Some(res) => {
647 #[test] 656 #[test]
648 fn test_leaked_borrow_after_mut() { 657 fn test_leaked_borrow_after_mut() {
649 let (gil, owner) = prepare_env(); 658 let (gil, owner) = prepare_env();
650 let py = gil.python(); 659 let py = gil.python();
651 let leaked = owner.string_shared(py).leak_immutable().unwrap(); 660 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(); 661 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()); 662 assert!(leaked.try_borrow(py).is_err());
656 } 663 }
657 664
658 #[test] 665 #[test]
659 fn test_leaked_borrow_mut_after_mut() { 666 fn test_leaked_borrow_mut_after_mut() {
660 let (gil, owner) = prepare_env(); 667 let (gil, owner) = prepare_env();
661 let py = gil.python(); 668 let py = gil.python();
662 let leaked = owner.string_shared(py).leak_immutable().unwrap(); 669 let leaked = owner.string_shared(py).leak_immutable().unwrap();
663 let mut leaked_iter = unsafe { leaked.map(py, |s| s.chars()) }; 670 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(); 671 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()); 672 assert!(leaked_iter.try_borrow_mut(py).is_err());
668 } 673 }
669 674
670 #[test] 675 #[test]
671 #[should_panic(expected = "map() over invalidated leaked reference")] 676 #[should_panic(expected = "map() over invalidated leaked reference")]
672 fn test_leaked_map_after_mut() { 677 fn test_leaked_map_after_mut() {
673 let (gil, owner) = prepare_env(); 678 let (gil, owner) = prepare_env();
674 let py = gil.python(); 679 let py = gil.python();
675 let leaked = owner.string_shared(py).leak_immutable().unwrap(); 680 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(); 681 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()) }; 682 let _leaked_iter = unsafe { leaked.map(py, |s| s.chars()) };
680 } 683 }
681 684
682 #[test] 685 #[test]
683 fn test_borrow_mut_while_leaked() { 686 fn test_borrow_mut_while_leaked_ref() {
684 let (gil, owner) = prepare_env(); 687 let (gil, owner) = prepare_env();
685 let py = gil.python(); 688 let py = gil.python();
686 assert!(owner.string_shared(py).borrow_mut().is_ok()); 689 assert!(owner.string_shared(py).borrow_mut().is_ok());
687 let _leaked = owner.string_shared(py).leak_immutable().unwrap(); 690 let leaked = owner.string_shared(py).leak_immutable().unwrap();
688 // TODO: will be allowed 691 {
689 assert!(owner.string_shared(py).borrow_mut().is_err()); 692 let _leaked_ref = leaked.try_borrow(py).unwrap();
690 } 693 assert!(owner.string_shared(py).borrow_mut().is_err());
691 } 694 {
695 let _leaked_ref2 = leaked.try_borrow(py).unwrap();
696 assert!(owner.string_shared(py).borrow_mut().is_err());
697 }
698 assert!(owner.string_shared(py).borrow_mut().is_err());
699 }
700 assert!(owner.string_shared(py).borrow_mut().is_ok());
701 }
702
703 #[test]
704 fn test_borrow_mut_while_leaked_ref_mut() {
705 let (gil, owner) = prepare_env();
706 let py = gil.python();
707 assert!(owner.string_shared(py).borrow_mut().is_ok());
708 let leaked = owner.string_shared(py).leak_immutable().unwrap();
709 let mut leaked_iter = unsafe { leaked.map(py, |s| s.chars()) };
710 {
711 let _leaked_ref = leaked_iter.try_borrow_mut(py).unwrap();
712 assert!(owner.string_shared(py).borrow_mut().is_err());
713 }
714 assert!(owner.string_shared(py).borrow_mut().is_ok());
715 }
716 }