diff --git a/src/pyclasses/cache.rs b/src/pyclasses/cache.rs index c866592..13fe4d4 100644 --- a/src/pyclasses/cache.rs +++ b/src/pyclasses/cache.rs @@ -542,52 +542,55 @@ impl PyCache { .map(|x| !x) } - fn items(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn items(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); let result = PyCacheItems { + cache: slf.as_any().clone().unbind(), // SAFETY: We cannot use lifetimes here, but we're tracking changes using [`GenerationVersion`] iter: parking_lot::Mutex::new(unsafe { inner.policy().table().iter() }), gv, initial_gv, }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } - fn values(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn values(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); let result = PyCacheValues { + cache: slf.as_any().clone().unbind(), // SAFETY: We cannot use lifetimes here, but we're tracking changes using [`GenerationVersion`] iter: parking_lot::Mutex::new(unsafe { inner.policy().table().iter() }), gv, initial_gv, }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } - fn keys(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn keys(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); let result = PyCacheKeys { + cache: slf.as_any().clone().unbind(), // SAFETY: We cannot use lifetimes here, but we're tracking changes using [`GenerationVersion`] iter: parking_lot::Mutex::new(unsafe { inner.policy().table().iter() }), gv, initial_gv, }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } #[inline] - fn __iter__(&self) -> pyo3::PyResult> { - self.keys() + fn __iter__(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + Self::keys(slf) } fn copy(&self, py: pyo3::Python) -> pyo3::PyResult> { @@ -681,6 +684,7 @@ macro_rules! implement_iterator { $( implement_pyclass! { [generic, frozen] $name as $pyname { + cache: pyo3::Py, initial_gv: u32, gv: utils::GenerationVersion, iter: parking_lot::Mutex>, @@ -694,6 +698,10 @@ macro_rules! implement_iterator { slf } + fn __traverse__(&self, visit: pyo3::PyVisit<'_>) -> Result<(), pyo3::PyTraverseError> { + visit.call(&self.cache) + } + fn __next__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyResult<$rt_type> { if slf.initial_gv != slf.gv.get() { return Err(new_py_error!( diff --git a/src/pyclasses/fifocache.rs b/src/pyclasses/fifocache.rs index 16d8d6e..3ee15b1 100644 --- a/src/pyclasses/fifocache.rs +++ b/src/pyclasses/fifocache.rs @@ -546,51 +546,54 @@ impl PyFIFOCache { .map(|x| !x) } - fn items(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn items(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); // SAFETY: We cannot use lifetimes here, but we're tracking changes using [`GenerationVersion`] let result = PyFIFOCacheItems { + cache: slf.as_any().clone().unbind(), iter: parking_lot::Mutex::new(inner.policy().iter()), gv, initial_gv, }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } - fn values(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn values(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); // SAFETY: We cannot use lifetimes here, but we're tracking changes using [`GenerationVersion`] let result = PyFIFOCacheValues { + cache: slf.as_any().clone().unbind(), iter: parking_lot::Mutex::new(inner.policy().iter()), gv, initial_gv, }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } - fn keys(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn keys(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); // SAFETY: We cannot use lifetimes here, but we're tracking changes using [`GenerationVersion`] let result = PyFIFOCacheKeys { + cache: slf.as_any().clone().unbind(), iter: parking_lot::Mutex::new(inner.policy().iter()), gv, initial_gv, }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } #[inline] - fn __iter__(&self) -> pyo3::PyResult> { - self.keys() + fn __iter__(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + Self::keys(slf) } fn copy(&self, py: pyo3::Python) -> pyo3::PyResult> { @@ -707,6 +710,7 @@ macro_rules! implement_iterator { $( implement_pyclass! { [generic, frozen] $name as $pyname { + cache: pyo3::Py, initial_gv: u32, gv: utils::GenerationVersion, iter: parking_lot::Mutex>, @@ -720,6 +724,10 @@ macro_rules! implement_iterator { slf } + fn __traverse__(&self, visit: pyo3::PyVisit<'_>) -> Result<(), pyo3::PyTraverseError> { + visit.call(&self.cache) + } + fn __next__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyResult<$rt_type> { if slf.initial_gv != slf.gv.get() { return Err(new_py_error!( diff --git a/src/pyclasses/lfucache.rs b/src/pyclasses/lfucache.rs index af80851..0d4a494 100644 --- a/src/pyclasses/lfucache.rs +++ b/src/pyclasses/lfucache.rs @@ -565,69 +565,75 @@ impl PyLFUCache { .map(|x| !x) } - fn items(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn items(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let mut policy = inner.policy(); let gv = inner.shared().generation_version(); let iter = policy.iter(gv); let result = PyLFUCacheItems { + cache: slf.as_any().clone().unbind(), iter: parking_lot::Mutex::new(iter), gv: gv.clone(), initial_gv: gv.get(), }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } - fn values(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn values(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let mut policy = inner.policy(); let gv = inner.shared().generation_version(); let iter = policy.iter(gv); let result = PyLFUCacheValues { + cache: slf.as_any().clone().unbind(), iter: parking_lot::Mutex::new(iter), gv: gv.clone(), initial_gv: gv.get(), }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } - fn keys(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn keys(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let mut policy = inner.policy(); let gv = inner.shared().generation_version(); let iter = policy.iter(gv); let result = PyLFUCacheKeys { + cache: slf.as_any().clone().unbind(), iter: parking_lot::Mutex::new(iter), gv: gv.clone(), initial_gv: gv.get(), }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } #[inline] - fn __iter__(&self) -> pyo3::PyResult> { - self.keys() + fn __iter__(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + Self::keys(slf) } - fn items_with_frequency(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn items_with_frequency( + slf: pyo3::Bound<'_, Self>, + ) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let mut policy = inner.policy(); let gv = inner.shared().generation_version(); let iter = policy.iter(gv); let result = PyLFUCacheItemsWithFrequency { + cache: slf.as_any().clone().unbind(), iter: parking_lot::Mutex::new(iter), gv: gv.clone(), initial_gv: gv.get(), }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } fn copy(&self, py: pyo3::Python) -> pyo3::PyResult> { @@ -771,6 +777,7 @@ macro_rules! implement_iterator { $( implement_pyclass! { [generic, frozen] $name as $pyname { + cache: pyo3::Py, initial_gv: u32, gv: utils::GenerationVersion, iter: parking_lot::Mutex>, @@ -784,6 +791,10 @@ macro_rules! implement_iterator { slf } + fn __traverse__(&self, visit: pyo3::PyVisit<'_>) -> Result<(), pyo3::PyTraverseError> { + visit.call(&self.cache) + } + fn __next__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyResult<$rt_type> { if slf.initial_gv != slf.gv.get() { return Err(new_py_error!( diff --git a/src/pyclasses/lrucache.rs b/src/pyclasses/lrucache.rs index f324e38..cb7c02f 100644 --- a/src/pyclasses/lrucache.rs +++ b/src/pyclasses/lrucache.rs @@ -573,51 +573,54 @@ impl PyLRUCache { .map(|x| !x) } - fn items(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn items(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); // SAFETY: We cannot use lifetimes here, but we're tracking changes using [`GenerationVersion`] let result = PyLRUCacheItems { + cache: slf.as_any().clone().unbind(), iter: parking_lot::Mutex::new(unsafe { inner.policy().list().iter() }), gv, initial_gv, }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } - fn values(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn values(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); // SAFETY: We cannot use lifetimes here, but we're tracking changes using [`GenerationVersion`] let result = PyLRUCacheValues { + cache: slf.as_any().clone().unbind(), iter: parking_lot::Mutex::new(unsafe { inner.policy().list().iter() }), gv, initial_gv, }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } - fn keys(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn keys(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); // SAFETY: We cannot use lifetimes here, but we're tracking changes using [`GenerationVersion`] let result = PyLRUCacheKeys { + cache: slf.as_any().clone().unbind(), iter: parking_lot::Mutex::new(unsafe { inner.policy().list().iter() }), gv, initial_gv, }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } #[inline] - fn __iter__(&self) -> pyo3::PyResult> { - self.keys() + fn __iter__(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + Self::keys(slf) } fn copy(&self, py: pyo3::Python) -> pyo3::PyResult> { @@ -755,6 +758,7 @@ macro_rules! implement_iterator { $( implement_pyclass! { [generic, frozen] $name as $pyname { + cache: pyo3::Py, initial_gv: u32, gv: utils::GenerationVersion, iter: parking_lot::Mutex>, @@ -768,6 +772,10 @@ macro_rules! implement_iterator { slf } + fn __traverse__(&self, visit: pyo3::PyVisit<'_>) -> Result<(), pyo3::PyTraverseError> { + visit.call(&self.cache) + } + fn __next__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyResult<$rt_type> { if slf.initial_gv != slf.gv.get() { return Err(new_py_error!( diff --git a/src/pyclasses/rrcache.rs b/src/pyclasses/rrcache.rs index ded54d4..63a6575 100644 --- a/src/pyclasses/rrcache.rs +++ b/src/pyclasses/rrcache.rs @@ -546,51 +546,54 @@ impl PyRRCache { .map(|x| !x) } - fn items(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn items(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); // SAFETY: We cannot use lifetimes here, but we're tracking changes using [`GenerationVersion`] let result = PyRRCacheItems { + cache: slf.as_any().clone().unbind(), iter: parking_lot::Mutex::new(unsafe { inner.policy().table().iter() }), gv, initial_gv, }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } - fn values(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn values(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); // SAFETY: We cannot use lifetimes here, but we're tracking changes using [`GenerationVersion`] let result = PyRRCacheValues { + cache: slf.as_any().clone().unbind(), iter: parking_lot::Mutex::new(unsafe { inner.policy().table().iter() }), gv, initial_gv, }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } - fn keys(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn keys(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); // SAFETY: We cannot use lifetimes here, but we're tracking changes using [`GenerationVersion`] let result = PyRRCacheKeys { + cache: slf.as_any().clone().unbind(), iter: parking_lot::Mutex::new(unsafe { inner.policy().table().iter() }), gv, initial_gv, }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } #[inline] - fn __iter__(&self) -> pyo3::PyResult> { - self.keys() + fn __iter__(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + Self::keys(slf) } fn copy(&self, py: pyo3::Python) -> pyo3::PyResult> { @@ -701,6 +704,7 @@ macro_rules! implement_iterator { $( implement_pyclass! { [generic, frozen] $name as $pyname { + cache: pyo3::Py, initial_gv: u32, gv: utils::GenerationVersion, iter: parking_lot::Mutex>, @@ -714,6 +718,10 @@ macro_rules! implement_iterator { slf } + fn __traverse__(&self, visit: pyo3::PyVisit<'_>) -> Result<(), pyo3::PyTraverseError> { + visit.call(&self.cache) + } + fn __next__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyResult<$rt_type> { if slf.initial_gv != slf.gv.get() { return Err(new_py_error!( diff --git a/src/pyclasses/ttlcache.rs b/src/pyclasses/ttlcache.rs index e45cf6e..992d0f0 100644 --- a/src/pyclasses/ttlcache.rs +++ b/src/pyclasses/ttlcache.rs @@ -551,8 +551,8 @@ impl PyTTLCache { .map(|x| !x) } - fn items(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn items(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let iter = inner.policy().iter(inner.shared()); @@ -561,15 +561,16 @@ impl PyTTLCache { // SAFETY: We cannot use lifetimes here, but we're tracking changes using [`GenerationVersion`] let result = PyTTLCacheItems { + cache: slf.as_any().clone().unbind(), iter: parking_lot::Mutex::new(iter), gv, initial_gv, }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } - fn values(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn values(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let iter = inner.policy().iter(inner.shared()); @@ -578,15 +579,16 @@ impl PyTTLCache { // SAFETY: We cannot use lifetimes here, but we're tracking changes using [`GenerationVersion`] let result = PyTTLCacheValues { + cache: slf.as_any().clone().unbind(), iter: parking_lot::Mutex::new(iter), gv, initial_gv, }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } - fn keys(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn keys(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let iter = inner.policy().iter(inner.shared()); @@ -595,16 +597,17 @@ impl PyTTLCache { // SAFETY: We cannot use lifetimes here, but we're tracking changes using [`GenerationVersion`] let result = PyTTLCacheKeys { + cache: slf.as_any().clone().unbind(), iter: parking_lot::Mutex::new(iter), gv, initial_gv, }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } #[inline] - fn __iter__(&self) -> pyo3::PyResult> { - self.keys() + fn __iter__(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + Self::keys(slf) } fn copy(&self, py: pyo3::Python) -> pyo3::PyResult> { @@ -787,8 +790,10 @@ impl PyTTLCache { Ok((key.into(), val, dur.as_secs_f64())) } - fn items_with_expire(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn items_with_expire( + slf: pyo3::Bound<'_, Self>, + ) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let iter = inner.policy().iter(inner.shared()); @@ -797,11 +802,12 @@ impl PyTTLCache { // SAFETY: We cannot use lifetimes here, but we're tracking changes using [`GenerationVersion`] let result = PyTTLCacheItemsWithExpire { + cache: slf.as_any().clone().unbind(), iter: parking_lot::Mutex::new(iter), gv, initial_gv, }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } fn __traverse__(&self, visit: pyo3::PyVisit<'_>) -> Result<(), pyo3::PyTraverseError> { @@ -841,6 +847,7 @@ macro_rules! implement_iterator { $( implement_pyclass! { [generic, frozen] $name as $pyname { + cache: pyo3::Py, initial_gv: u32, gv: utils::GenerationVersion, iter: parking_lot::Mutex>, @@ -854,6 +861,10 @@ macro_rules! implement_iterator { slf } + fn __traverse__(&self, visit: pyo3::PyVisit<'_>) -> Result<(), pyo3::PyTraverseError> { + visit.call(&self.cache) + } + fn __next__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyResult<$rt_type> { if slf.initial_gv != slf.gv.get() { return Err(new_py_error!( diff --git a/src/pyclasses/vttlcache.rs b/src/pyclasses/vttlcache.rs index a738da5..0644453 100644 --- a/src/pyclasses/vttlcache.rs +++ b/src/pyclasses/vttlcache.rs @@ -525,54 +525,57 @@ impl PyVTTLCache { .map(|x| !x) } - fn items(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn items(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let mut policy = inner.policy(); let gv = inner.shared().generation_version(); let iter = policy.iter(gv); let result = PyVTTLCacheItems { + cache: slf.as_any().clone().unbind(), iter: parking_lot::Mutex::new(iter), gv: gv.clone(), initial_gv: gv.get(), }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } - fn values(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn values(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let mut policy = inner.policy(); let gv = inner.shared().generation_version(); let iter = policy.iter(gv); let result = PyVTTLCacheValues { + cache: slf.as_any().clone().unbind(), iter: parking_lot::Mutex::new(iter), gv: gv.clone(), initial_gv: gv.get(), }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } - fn keys(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn keys(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let mut policy = inner.policy(); let gv = inner.shared().generation_version(); let iter = policy.iter(gv); let result = PyVTTLCacheKeys { + cache: slf.as_any().clone().unbind(), iter: parking_lot::Mutex::new(iter), gv: gv.clone(), initial_gv: gv.get(), }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } #[inline] - fn __iter__(&self) -> pyo3::PyResult> { - self.keys() + fn __iter__(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { + Self::keys(slf) } fn copy(&self, py: pyo3::Python) -> pyo3::PyResult> { @@ -749,19 +752,22 @@ impl PyVTTLCache { Ok((key.into(), val, dur)) } - fn items_with_expire(&self) -> pyo3::PyResult> { - let inner = self.0.get(); + fn items_with_expire( + slf: pyo3::Bound<'_, Self>, + ) -> pyo3::PyResult> { + let inner = slf.get().0.get(); let mut policy = inner.policy(); let gv = inner.shared().generation_version(); let iter = policy.iter(gv); let result = PyVTTLCacheItemsWithExpire { + cache: slf.as_any().clone().unbind(), iter: parking_lot::Mutex::new(iter), gv: gv.clone(), initial_gv: gv.get(), }; - pyo3::Python::attach(|py| pyo3::Py::new(py, result)) + pyo3::Py::new(slf.py(), result) } fn __traverse__(&self, visit: pyo3::PyVisit<'_>) -> Result<(), pyo3::PyTraverseError> { @@ -803,6 +809,7 @@ macro_rules! implement_iterator { $( implement_pyclass! { [generic, frozen] $name as $pyname { + cache: pyo3::Py, initial_gv: u32, gv: utils::GenerationVersion, iter: parking_lot::Mutex>, @@ -816,6 +823,10 @@ macro_rules! implement_iterator { slf } + fn __traverse__(&self, visit: pyo3::PyVisit<'_>) -> Result<(), pyo3::PyTraverseError> { + visit.call(&self.cache) + } + fn __next__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyResult<$rt_type> { if slf.initial_gv != slf.gv.get() { return Err(new_py_error!( diff --git a/tests/mixins.py b/tests/mixins.py index f04f8c7..0ae7e0c 100644 --- a/tests/mixins.py +++ b/tests/mixins.py @@ -2,11 +2,13 @@ import dataclasses import gc import pickle +import platform import subprocess import sys import threading import time import typing +import weakref from datetime import timedelta from unittest.mock import Mock, patch @@ -440,6 +442,29 @@ def test_ne_different_contents(self): assert c1 != c2 +WALKING_A_TEMPORARY_CACHE = """ +import sys + +import cachebox + +name = sys.argv[1] +cls = getattr(cachebox, name) + + +def make(): + cache = cls(10, global_ttl=60) if name == "TTLCache" else cls(10) + cache.update({"a": 1, "b": 2}) + return cache + + +# nothing else holds the cache by the time the walk starts +assert set(make().keys()) == {"a", "b"} +assert set(make().items()) == {("a", 1), ("b", 2)} +assert sorted(make().values()) == [1, 2] +print("ok") +""" + + class IterationMixin(BaseMixin): def test_keys_returns_all_keys(self): cache = self.create_cache() @@ -465,6 +490,39 @@ def test_iter_yields_keys(self): cache.update({"x": 10, "y": 20}) assert set(iter(cache)) == {"x", "y"} + def test_walking_a_cache_nothing_else_holds(self): + # reading freed memory faults instead of failing, so it runs in a child process + name = type(self.create_cache()).__name__ + + done = subprocess.run( + [sys.executable, "-c", WALKING_A_TEMPORARY_CACHE, name], + capture_output=True, + text=True, + timeout=60, + ) + + assert done.stdout.strip() == "ok", done.stderr or f"exit code {done.returncode}" + + @pytest.mark.skipif( + platform.python_implementation() == "PyPy", + reason="PyPy's GC does not collect cycles through the cache objects: " + "a cache holding itself is not collected either", + ) + def test_cache_holding_its_own_iterator_is_collected(self): + class Canary: + pass + + canary = Canary() + ref = weakref.ref(canary) + + cache = self.create_cache() + cache.insert("canary", canary) + cache.insert("self", cache.keys()) # the cycle: cache -> iterator -> cache + del cache, canary + gc.collect() + + assert ref() is None + def test_generation_version_on_remove(self): cache = self.create_cache(10, {i: i for i in range(10)})