|
3 | 3 | //! Interaction with python's global interpreter lock
|
4 | 4 |
|
5 | 5 | use crate::{ffi, internal_tricks::Unsendable, Python};
|
6 |
| -use std::cell::{Cell, RefCell, UnsafeCell}; |
7 |
| -use std::sync::atomic::{spin_loop_hint, AtomicBool, Ordering}; |
| 6 | +use parking_lot::{const_mutex, Mutex}; |
| 7 | +use std::cell::{Cell, RefCell}; |
8 | 8 | use std::{any, mem::ManuallyDrop, ptr::NonNull, sync};
|
9 | 9 |
|
10 | 10 | static START: sync::Once = sync::Once::new();
|
@@ -168,75 +168,49 @@ impl Drop for GILGuard {
|
168 | 168 |
|
169 | 169 | /// Thread-safe storage for objects which were inc_ref / dec_ref while the GIL was not held.
|
170 | 170 | struct ReferencePool {
|
171 |
| - locked: AtomicBool, |
172 |
| - pointers_to_incref: UnsafeCell<Vec<NonNull<ffi::PyObject>>>, |
173 |
| - pointers_to_decref: UnsafeCell<Vec<NonNull<ffi::PyObject>>>, |
174 |
| -} |
175 |
| - |
176 |
| -struct Lock<'a> { |
177 |
| - lock: &'a AtomicBool, |
178 |
| -} |
179 |
| - |
180 |
| -impl<'a> Lock<'a> { |
181 |
| - fn new(lock: &'a AtomicBool) -> Self { |
182 |
| - while lock.compare_and_swap(false, true, Ordering::Acquire) { |
183 |
| - spin_loop_hint(); |
184 |
| - } |
185 |
| - Self { lock } |
186 |
| - } |
187 |
| -} |
188 |
| - |
189 |
| -impl<'a> Drop for Lock<'a> { |
190 |
| - fn drop(&mut self) { |
191 |
| - self.lock.store(false, Ordering::Release); |
192 |
| - } |
| 171 | + pointers_to_incref: Mutex<Vec<NonNull<ffi::PyObject>>>, |
| 172 | + pointers_to_decref: Mutex<Vec<NonNull<ffi::PyObject>>>, |
193 | 173 | }
|
194 | 174 |
|
195 | 175 | impl ReferencePool {
|
196 | 176 | const fn new() -> Self {
|
197 | 177 | Self {
|
198 |
| - locked: AtomicBool::new(false), |
199 |
| - pointers_to_incref: UnsafeCell::new(Vec::new()), |
200 |
| - pointers_to_decref: UnsafeCell::new(Vec::new()), |
| 178 | + pointers_to_incref: const_mutex(Vec::new()), |
| 179 | + pointers_to_decref: const_mutex(Vec::new()), |
201 | 180 | }
|
202 | 181 | }
|
203 | 182 |
|
204 | 183 | fn register_incref(&self, obj: NonNull<ffi::PyObject>) {
|
205 |
| - let _lock = Lock::new(&self.locked); |
206 |
| - let v = self.pointers_to_incref.get(); |
207 |
| - unsafe { (*v).push(obj) }; |
| 184 | + self.pointers_to_incref.lock().push(obj) |
208 | 185 | }
|
209 | 186 |
|
210 | 187 | fn register_decref(&self, obj: NonNull<ffi::PyObject>) {
|
211 |
| - let _lock = Lock::new(&self.locked); |
212 |
| - let v = self.pointers_to_decref.get(); |
213 |
| - unsafe { (*v).push(obj) }; |
| 188 | + self.pointers_to_decref.lock().push(obj) |
214 | 189 | }
|
215 | 190 |
|
216 | 191 | fn update_counts(&self, _py: Python) {
|
217 |
| - let _lock = Lock::new(&self.locked); |
| 192 | + macro_rules! swap_vec_with_lock { |
| 193 | + // Get vec from one of ReferencePool's mutexes via lock, swap vec if needed, unlock. |
| 194 | + ($cell:expr) => {{ |
| 195 | + let mut locked = $cell.lock(); |
| 196 | + let mut out = Vec::new(); |
| 197 | + if !locked.is_empty() { |
| 198 | + std::mem::swap(&mut out, &mut *locked); |
| 199 | + } |
| 200 | + drop(locked); |
| 201 | + out |
| 202 | + }}; |
| 203 | + }; |
218 | 204 |
|
219 | 205 | // Always increase reference counts first - as otherwise objects which have a
|
220 | 206 | // nonzero total reference count might be incorrectly dropped by Python during
|
221 | 207 | // this update.
|
222 |
| - { |
223 |
| - let v = self.pointers_to_incref.get(); |
224 |
| - unsafe { |
225 |
| - for ptr in &(*v) { |
226 |
| - ffi::Py_INCREF(ptr.as_ptr()); |
227 |
| - } |
228 |
| - (*v).clear(); |
229 |
| - } |
| 208 | + for ptr in swap_vec_with_lock!(self.pointers_to_incref) { |
| 209 | + unsafe { ffi::Py_INCREF(ptr.as_ptr()) }; |
230 | 210 | }
|
231 | 211 |
|
232 |
| - { |
233 |
| - let v = self.pointers_to_decref.get(); |
234 |
| - unsafe { |
235 |
| - for ptr in &(*v) { |
236 |
| - ffi::Py_DECREF(ptr.as_ptr()); |
237 |
| - } |
238 |
| - (*v).clear(); |
239 |
| - } |
| 212 | + for ptr in swap_vec_with_lock!(self.pointers_to_decref) { |
| 213 | + unsafe { ffi::Py_DECREF(ptr.as_ptr()) }; |
240 | 214 | }
|
241 | 215 | }
|
242 | 216 | }
|
@@ -637,17 +611,48 @@ mod test {
|
637 | 611 |
|
638 | 612 | // The pointer should appear once in the incref pool, and once in the
|
639 | 613 | // decref pool (for the clone being created and also dropped)
|
640 |
| - assert_eq!(unsafe { &*POOL.pointers_to_incref.get() }, &vec![ptr]); |
641 |
| - assert_eq!(unsafe { &*POOL.pointers_to_decref.get() }, &vec![ptr]); |
| 614 | + assert_eq!(&*POOL.pointers_to_incref.lock(), &vec![ptr]); |
| 615 | + assert_eq!(&*POOL.pointers_to_decref.lock(), &vec![ptr]); |
642 | 616 |
|
643 | 617 | // Re-acquring GIL will clear these pending changes
|
644 | 618 | drop(gil);
|
645 | 619 | let _gil = Python::acquire_gil();
|
646 | 620 |
|
647 |
| - assert!(unsafe { (*POOL.pointers_to_incref.get()).is_empty() }); |
648 |
| - assert!(unsafe { (*POOL.pointers_to_decref.get()).is_empty() }); |
| 621 | + assert!(POOL.pointers_to_incref.lock().is_empty()); |
| 622 | + assert!(POOL.pointers_to_decref.lock().is_empty()); |
649 | 623 |
|
650 | 624 | // Overall count is still unchanged
|
651 | 625 | assert_eq!(count, obj.get_refcnt());
|
652 | 626 | }
|
| 627 | + |
| 628 | + #[test] |
| 629 | + fn test_update_counts_does_not_deadlock() { |
| 630 | + // update_counts can run arbitrary Python code during Py_DECREF. |
| 631 | + // if the locking is implemented incorrectly, it will deadlock. |
| 632 | + |
| 633 | + let gil = Python::acquire_gil(); |
| 634 | + let obj = get_object(gil.python()); |
| 635 | + |
| 636 | + unsafe { |
| 637 | + unsafe extern "C" fn capsule_drop(capsule: *mut ffi::PyObject) { |
| 638 | + // This line will implicitly call update_counts |
| 639 | + // -> and so cause deadlock if update_counts is not handling recursion correctly. |
| 640 | + let pool = GILPool::new(); |
| 641 | + |
| 642 | + // Rebuild obj so that it can be dropped |
| 643 | + PyObject::from_owned_ptr( |
| 644 | + pool.python(), |
| 645 | + ffi::PyCapsule_GetPointer(capsule, std::ptr::null()) as _, |
| 646 | + ); |
| 647 | + } |
| 648 | + |
| 649 | + let ptr = obj.into_ptr(); |
| 650 | + let capsule = ffi::PyCapsule_New(ptr as _, std::ptr::null(), Some(capsule_drop)); |
| 651 | + |
| 652 | + POOL.register_decref(NonNull::new(capsule).unwrap()); |
| 653 | + |
| 654 | + // Updating the counts will call decref on the capsule, which calls capsule_drop |
| 655 | + POOL.update_counts(gil.python()) |
| 656 | + } |
| 657 | + } |
653 | 658 | }
|
0 commit comments