2021-12-03 00:03:32 +00:00
|
|
|
#![cfg(feature = "macros")]
|
|
|
|
|
2018-09-21 21:32:48 +00:00
|
|
|
use pyo3::class::PyTraverseError;
|
|
|
|
use pyo3::class::PyVisit;
|
2018-05-02 18:49:40 +00:00
|
|
|
use pyo3::prelude::*;
|
2020-03-26 05:50:00 +00:00
|
|
|
use pyo3::{py_run, AsPyPointer, PyCell, PyTryInto};
|
2019-02-01 13:01:18 +00:00
|
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
|
|
use std::sync::Arc;
|
2018-05-02 18:49:40 +00:00
|
|
|
|
|
|
|
mod common;
|
|
|
|
|
2018-07-08 21:33:48 +00:00
|
|
|
#[pyclass(freelist = 2)]
|
2018-11-12 12:59:19 +00:00
|
|
|
struct ClassWithFreelist {}
|
2018-05-02 18:49:40 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn class_with_freelist() {
|
2022-07-19 17:34:23 +00:00
|
|
|
let ptr = Python::with_gil(|py| {
|
2019-02-07 18:07:09 +00:00
|
|
|
let inst = Py::new(py, ClassWithFreelist {}).unwrap();
|
|
|
|
let _inst2 = Py::new(py, ClassWithFreelist {}).unwrap();
|
2022-07-19 17:34:23 +00:00
|
|
|
let ptr = inst.as_ptr();
|
2018-05-02 18:49:40 +00:00
|
|
|
drop(inst);
|
2022-07-19 17:34:23 +00:00
|
|
|
ptr
|
|
|
|
});
|
2018-05-02 18:49:40 +00:00
|
|
|
|
2022-07-19 17:34:23 +00:00
|
|
|
Python::with_gil(|py| {
|
2019-02-07 18:07:09 +00:00
|
|
|
let inst3 = Py::new(py, ClassWithFreelist {}).unwrap();
|
2018-05-02 18:49:40 +00:00
|
|
|
assert_eq!(ptr, inst3.as_ptr());
|
|
|
|
|
2019-02-07 18:07:09 +00:00
|
|
|
let inst4 = Py::new(py, ClassWithFreelist {}).unwrap();
|
2018-05-02 18:49:40 +00:00
|
|
|
assert_ne!(ptr, inst4.as_ptr())
|
2022-07-19 17:34:23 +00:00
|
|
|
});
|
2018-05-02 18:49:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
struct TestDropCall {
|
2018-06-15 19:21:12 +00:00
|
|
|
drop_called: Arc<AtomicBool>,
|
2018-05-02 18:49:40 +00:00
|
|
|
}
|
2018-10-03 19:04:24 +00:00
|
|
|
|
2018-05-02 18:49:40 +00:00
|
|
|
impl Drop for TestDropCall {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
self.drop_called.store(true, Ordering::Relaxed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(dead_code)]
|
2018-07-08 21:33:48 +00:00
|
|
|
#[pyclass]
|
2018-05-02 18:49:40 +00:00
|
|
|
struct DataIsDropped {
|
|
|
|
member1: TestDropCall,
|
|
|
|
member2: TestDropCall,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn data_is_dropped() {
|
|
|
|
let drop_called1 = Arc::new(AtomicBool::new(false));
|
|
|
|
let drop_called2 = Arc::new(AtomicBool::new(false));
|
|
|
|
|
2022-07-19 17:34:23 +00:00
|
|
|
Python::with_gil(|py| {
|
2019-02-13 20:35:26 +00:00
|
|
|
let data_is_dropped = DataIsDropped {
|
|
|
|
member1: TestDropCall {
|
|
|
|
drop_called: Arc::clone(&drop_called1),
|
|
|
|
},
|
|
|
|
member2: TestDropCall {
|
|
|
|
drop_called: Arc::clone(&drop_called2),
|
|
|
|
},
|
|
|
|
};
|
|
|
|
let inst = Py::new(py, data_is_dropped).unwrap();
|
2018-05-02 18:49:40 +00:00
|
|
|
assert!(!drop_called1.load(Ordering::Relaxed));
|
|
|
|
assert!(!drop_called2.load(Ordering::Relaxed));
|
|
|
|
drop(inst);
|
2022-07-19 17:34:23 +00:00
|
|
|
});
|
2018-05-02 18:49:40 +00:00
|
|
|
|
|
|
|
assert!(drop_called1.load(Ordering::Relaxed));
|
|
|
|
assert!(drop_called2.load(Ordering::Relaxed));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(dead_code)]
|
2018-07-08 21:33:48 +00:00
|
|
|
#[pyclass]
|
2021-03-26 05:21:38 +00:00
|
|
|
struct GcIntegration {
|
2020-05-31 15:15:33 +00:00
|
|
|
self_ref: PyObject,
|
2018-05-02 18:49:40 +00:00
|
|
|
dropped: TestDropCall,
|
|
|
|
}
|
|
|
|
|
2022-02-10 21:28:42 +00:00
|
|
|
#[pymethods]
|
|
|
|
impl GcIntegration {
|
2022-03-23 07:07:28 +00:00
|
|
|
fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
|
2020-05-31 15:15:33 +00:00
|
|
|
visit.call(&self.self_ref)
|
2018-05-02 18:49:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn __clear__(&mut self) {
|
2022-07-19 17:34:23 +00:00
|
|
|
Python::with_gil(|py| {
|
|
|
|
self.self_ref = py.None();
|
|
|
|
});
|
2018-05-02 18:49:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn gc_integration() {
|
|
|
|
let drop_called = Arc::new(AtomicBool::new(false));
|
|
|
|
|
2022-07-19 17:34:23 +00:00
|
|
|
Python::with_gil(|py| {
|
2020-02-15 04:42:25 +00:00
|
|
|
let inst = PyCell::new(
|
2019-02-07 18:07:09 +00:00
|
|
|
py,
|
2021-03-26 05:21:38 +00:00
|
|
|
GcIntegration {
|
2020-05-31 15:15:33 +00:00
|
|
|
self_ref: py.None(),
|
2019-02-07 18:07:09 +00:00
|
|
|
dropped: TestDropCall {
|
|
|
|
drop_called: Arc::clone(&drop_called),
|
|
|
|
},
|
2018-06-15 19:21:12 +00:00
|
|
|
},
|
2019-02-07 18:07:09 +00:00
|
|
|
)
|
2018-09-28 21:34:57 +00:00
|
|
|
.unwrap();
|
2018-05-02 18:49:40 +00:00
|
|
|
|
2020-02-15 15:24:38 +00:00
|
|
|
let mut borrow = inst.borrow_mut();
|
2020-05-31 15:15:33 +00:00
|
|
|
borrow.self_ref = inst.to_object(py);
|
2022-02-28 08:03:54 +00:00
|
|
|
|
|
|
|
py_run!(py, inst, "import gc; assert inst in gc.get_objects()");
|
2022-07-19 17:34:23 +00:00
|
|
|
});
|
2018-05-02 18:49:40 +00:00
|
|
|
|
2022-07-19 17:34:23 +00:00
|
|
|
Python::with_gil(|py| {
|
|
|
|
py.run("import gc; gc.collect()", None, None).unwrap();
|
|
|
|
assert!(drop_called.load(Ordering::Relaxed));
|
|
|
|
});
|
2018-05-02 18:49:40 +00:00
|
|
|
}
|
|
|
|
|
2023-01-27 00:25:18 +00:00
|
|
|
#[pyclass]
|
|
|
|
struct GcNullTraversal {
|
|
|
|
cycle: Option<Py<Self>>,
|
|
|
|
null: Option<Py<Self>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[pymethods]
|
|
|
|
impl GcNullTraversal {
|
|
|
|
fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
|
|
|
|
visit.call(&self.cycle)?;
|
|
|
|
visit.call(&self.null)?; // Should not segfault
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn __clear__(&mut self) {
|
|
|
|
self.cycle = None;
|
|
|
|
self.null = None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn gc_null_traversal() {
|
|
|
|
Python::with_gil(|py| {
|
|
|
|
let obj = Py::new(
|
|
|
|
py,
|
|
|
|
GcNullTraversal {
|
|
|
|
cycle: None,
|
|
|
|
null: None,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
obj.borrow_mut(py).cycle = Some(obj.clone_ref(py));
|
|
|
|
|
|
|
|
// the object doesn't have to be cleaned up, it just needs to be traversed.
|
|
|
|
py.run("import gc; gc.collect()", None, None).unwrap();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-09-01 00:36:26 +00:00
|
|
|
#[pyclass(subclass)]
|
2018-05-02 18:49:40 +00:00
|
|
|
struct BaseClassWithDrop {
|
|
|
|
data: Option<Arc<AtomicBool>>,
|
|
|
|
}
|
|
|
|
|
2018-07-08 21:33:48 +00:00
|
|
|
#[pymethods]
|
2018-05-02 18:49:40 +00:00
|
|
|
impl BaseClassWithDrop {
|
|
|
|
#[new]
|
2019-12-14 14:16:39 +00:00
|
|
|
fn new() -> BaseClassWithDrop {
|
|
|
|
BaseClassWithDrop { data: None }
|
2018-05-02 18:49:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for BaseClassWithDrop {
|
|
|
|
fn drop(&mut self) {
|
2020-11-12 11:09:24 +00:00
|
|
|
if let Some(data) = &self.data {
|
2018-05-02 18:49:40 +00:00
|
|
|
data.store(true, Ordering::Relaxed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-03 19:04:24 +00:00
|
|
|
#[pyclass(extends = BaseClassWithDrop)]
|
2018-05-02 18:49:40 +00:00
|
|
|
struct SubClassWithDrop {
|
|
|
|
data: Option<Arc<AtomicBool>>,
|
|
|
|
}
|
|
|
|
|
2018-07-08 21:33:48 +00:00
|
|
|
#[pymethods]
|
2018-05-02 18:49:40 +00:00
|
|
|
impl SubClassWithDrop {
|
|
|
|
#[new]
|
2020-01-05 07:01:05 +00:00
|
|
|
fn new() -> (Self, BaseClassWithDrop) {
|
|
|
|
(
|
|
|
|
SubClassWithDrop { data: None },
|
|
|
|
BaseClassWithDrop { data: None },
|
|
|
|
)
|
2018-05-02 18:49:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for SubClassWithDrop {
|
|
|
|
fn drop(&mut self) {
|
2020-11-12 11:09:24 +00:00
|
|
|
if let Some(data) = &self.data {
|
2018-05-02 18:49:40 +00:00
|
|
|
data.store(true, Ordering::Relaxed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn inheritance_with_new_methods_with_drop() {
|
|
|
|
let drop_called1 = Arc::new(AtomicBool::new(false));
|
|
|
|
let drop_called2 = Arc::new(AtomicBool::new(false));
|
|
|
|
|
2022-07-19 17:34:23 +00:00
|
|
|
Python::with_gil(|py| {
|
2018-05-02 18:49:40 +00:00
|
|
|
let _typebase = py.get_type::<BaseClassWithDrop>();
|
|
|
|
let typeobj = py.get_type::<SubClassWithDrop>();
|
2019-02-23 17:01:22 +00:00
|
|
|
let inst = typeobj.call((), None).unwrap();
|
2018-05-02 18:49:40 +00:00
|
|
|
|
2022-08-12 03:50:23 +00:00
|
|
|
let obj: &PyCell<SubClassWithDrop> = PyTryInto::try_into(inst).unwrap();
|
2020-02-15 04:42:25 +00:00
|
|
|
let mut obj_ref_mut = obj.borrow_mut();
|
|
|
|
obj_ref_mut.data = Some(Arc::clone(&drop_called1));
|
2020-05-03 12:12:51 +00:00
|
|
|
let base: &mut BaseClassWithDrop = obj_ref_mut.as_mut();
|
|
|
|
base.data = Some(Arc::clone(&drop_called2));
|
2022-07-19 17:34:23 +00:00
|
|
|
});
|
2018-05-02 18:49:40 +00:00
|
|
|
|
|
|
|
assert!(drop_called1.load(Ordering::Relaxed));
|
|
|
|
assert!(drop_called2.load(Ordering::Relaxed));
|
|
|
|
}
|
2020-04-08 22:49:51 +00:00
|
|
|
|
2022-02-11 08:31:06 +00:00
|
|
|
#[pyclass]
|
2020-04-08 22:49:51 +00:00
|
|
|
struct TraversableClass {
|
|
|
|
traversed: AtomicBool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TraversableClass {
|
|
|
|
fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
traversed: AtomicBool::new(false),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-10 21:28:42 +00:00
|
|
|
#[pymethods]
|
|
|
|
impl TraversableClass {
|
2020-04-08 22:49:51 +00:00
|
|
|
fn __clear__(&mut self) {}
|
2022-02-28 08:03:54 +00:00
|
|
|
|
|
|
|
#[allow(clippy::unnecessary_wraps)]
|
2022-03-23 07:07:28 +00:00
|
|
|
fn __traverse__(&self, _visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
|
2020-04-08 22:49:51 +00:00
|
|
|
self.traversed.store(true, Ordering::Relaxed);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-15 12:21:49 +00:00
|
|
|
unsafe fn get_type_traverse(tp: *mut pyo3::ffi::PyTypeObject) -> Option<pyo3::ffi::traverseproc> {
|
|
|
|
std::mem::transmute(pyo3::ffi::PyType_GetSlot(tp, pyo3::ffi::Py_tp_traverse))
|
|
|
|
}
|
|
|
|
|
2020-04-08 22:49:51 +00:00
|
|
|
#[test]
|
|
|
|
fn gc_during_borrow() {
|
2022-07-19 17:34:23 +00:00
|
|
|
Python::with_gil(|py| {
|
|
|
|
unsafe {
|
|
|
|
// declare a dummy visitor function
|
|
|
|
extern "C" fn novisit(
|
|
|
|
_object: *mut pyo3::ffi::PyObject,
|
|
|
|
_arg: *mut core::ffi::c_void,
|
|
|
|
) -> std::os::raw::c_int {
|
|
|
|
0
|
|
|
|
}
|
|
|
|
|
|
|
|
// get the traverse function
|
2022-11-19 08:17:40 +00:00
|
|
|
let ty = py.get_type::<TraversableClass>().as_type_ptr();
|
2022-07-19 17:34:23 +00:00
|
|
|
let traverse = get_type_traverse(ty).unwrap();
|
|
|
|
|
|
|
|
// create an object and check that traversing it works normally
|
|
|
|
// when it's not borrowed
|
|
|
|
let cell = PyCell::new(py, TraversableClass::new()).unwrap();
|
|
|
|
let obj = cell.to_object(py);
|
|
|
|
assert!(!cell.borrow().traversed.load(Ordering::Relaxed));
|
|
|
|
traverse(obj.as_ptr(), novisit, std::ptr::null_mut());
|
|
|
|
assert!(cell.borrow().traversed.load(Ordering::Relaxed));
|
|
|
|
|
|
|
|
// create an object and check that it is not traversed if the GC
|
|
|
|
// is invoked while it is already borrowed mutably
|
|
|
|
let cell2 = PyCell::new(py, TraversableClass::new()).unwrap();
|
|
|
|
let obj2 = cell2.to_object(py);
|
|
|
|
let guard = cell2.borrow_mut();
|
|
|
|
assert!(!guard.traversed.load(Ordering::Relaxed));
|
|
|
|
traverse(obj2.as_ptr(), novisit, std::ptr::null_mut());
|
|
|
|
assert!(!guard.traversed.load(Ordering::Relaxed));
|
|
|
|
drop(guard);
|
2020-04-08 22:49:51 +00:00
|
|
|
}
|
2022-07-19 17:34:23 +00:00
|
|
|
});
|
2020-04-08 22:49:51 +00:00
|
|
|
}
|
2022-02-26 22:18:28 +00:00
|
|
|
|
|
|
|
#[pyclass]
|
|
|
|
struct PanickyTraverse {
|
|
|
|
member: PyObject,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PanickyTraverse {
|
2022-03-23 07:07:28 +00:00
|
|
|
fn new(py: Python<'_>) -> Self {
|
2022-02-26 22:18:28 +00:00
|
|
|
Self { member: py.None() }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[pymethods]
|
|
|
|
impl PanickyTraverse {
|
2022-03-23 07:07:28 +00:00
|
|
|
fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
|
2022-02-26 22:18:28 +00:00
|
|
|
visit.call(&self.member)?;
|
|
|
|
// In the test, we expect this to never be hit
|
|
|
|
unreachable!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn traverse_error() {
|
|
|
|
Python::with_gil(|py| unsafe {
|
|
|
|
// declare a visitor function which errors (returns nonzero code)
|
|
|
|
extern "C" fn visit_error(
|
|
|
|
_object: *mut pyo3::ffi::PyObject,
|
|
|
|
_arg: *mut core::ffi::c_void,
|
|
|
|
) -> std::os::raw::c_int {
|
|
|
|
-1
|
|
|
|
}
|
|
|
|
|
|
|
|
// get the traverse function
|
2022-11-19 08:17:40 +00:00
|
|
|
let ty = py.get_type::<PanickyTraverse>().as_type_ptr();
|
2022-02-26 22:18:28 +00:00
|
|
|
let traverse = get_type_traverse(ty).unwrap();
|
|
|
|
|
|
|
|
// confirm that traversing errors
|
|
|
|
let obj = Py::new(py, PanickyTraverse::new(py)).unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
traverse(obj.as_ptr(), visit_error, std::ptr::null_mut()),
|
|
|
|
-1
|
|
|
|
);
|
|
|
|
})
|
|
|
|
}
|
2023-05-20 11:35:41 +00:00
|
|
|
|
|
|
|
#[pyclass]
|
|
|
|
struct TriesGILInTraverse {}
|
|
|
|
|
|
|
|
#[pymethods]
|
|
|
|
impl TriesGILInTraverse {
|
|
|
|
fn __traverse__(&self, _visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
|
2023-05-20 12:36:57 +00:00
|
|
|
Python::with_gil(|_py| Ok(()))
|
2023-05-20 11:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn tries_gil_in_traverse() {
|
|
|
|
Python::with_gil(|py| unsafe {
|
|
|
|
// declare a visitor function which errors (returns nonzero code)
|
|
|
|
extern "C" fn novisit(
|
|
|
|
_object: *mut pyo3::ffi::PyObject,
|
|
|
|
_arg: *mut core::ffi::c_void,
|
|
|
|
) -> std::os::raw::c_int {
|
|
|
|
0
|
|
|
|
}
|
|
|
|
|
|
|
|
// get the traverse function
|
|
|
|
let ty = py.get_type::<TriesGILInTraverse>().as_type_ptr();
|
|
|
|
let traverse = get_type_traverse(ty).unwrap();
|
|
|
|
|
|
|
|
// confirm that traversing panicks
|
|
|
|
let obj = Py::new(py, TriesGILInTraverse {}).unwrap();
|
|
|
|
assert_eq!(traverse(obj.as_ptr(), novisit, std::ptr::null_mut()), -1);
|
|
|
|
})
|
|
|
|
}
|