Mercurial > public > mercurial-scm > hg
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 } |