cachebox 6.2.6__tar.gz → 6.2.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. {cachebox-6.2.6 → cachebox-6.2.7}/.github/workflows/CI.yml +22 -16
  2. {cachebox-6.2.6 → cachebox-6.2.7}/Cargo.lock +1 -1
  3. {cachebox-6.2.6 → cachebox-6.2.7}/Cargo.toml +1 -1
  4. {cachebox-6.2.6 → cachebox-6.2.7}/PKG-INFO +1 -1
  5. {cachebox-6.2.6 → cachebox-6.2.7}/src/policies/fifopolicy.rs +2 -2
  6. {cachebox-6.2.6 → cachebox-6.2.7}/src/policies/lfupolicy.rs +2 -2
  7. {cachebox-6.2.6 → cachebox-6.2.7}/src/policies/lrupolicy.rs +2 -2
  8. {cachebox-6.2.6 → cachebox-6.2.7}/src/policies/nopolicy.rs +2 -2
  9. {cachebox-6.2.6 → cachebox-6.2.7}/src/policies/rrpolicy.rs +2 -2
  10. {cachebox-6.2.6 → cachebox-6.2.7}/src/policies/traits.rs +1 -1
  11. {cachebox-6.2.6 → cachebox-6.2.7}/src/policies/ttlpolicy.rs +2 -2
  12. {cachebox-6.2.6 → cachebox-6.2.7}/src/policies/vttlpolicy.rs +2 -2
  13. {cachebox-6.2.6 → cachebox-6.2.7}/src/policies/wrapped.rs +12 -2
  14. {cachebox-6.2.6 → cachebox-6.2.7}/src/pyclasses/cache.rs +3 -1
  15. {cachebox-6.2.6 → cachebox-6.2.7}/src/pyclasses/fifocache.rs +3 -1
  16. {cachebox-6.2.6 → cachebox-6.2.7}/src/pyclasses/lfucache.rs +3 -1
  17. {cachebox-6.2.6 → cachebox-6.2.7}/src/pyclasses/lrucache.rs +3 -1
  18. {cachebox-6.2.6 → cachebox-6.2.7}/src/pyclasses/rrcache.rs +3 -1
  19. {cachebox-6.2.6 → cachebox-6.2.7}/src/pyclasses/ttlcache.rs +3 -1
  20. {cachebox-6.2.6 → cachebox-6.2.7}/src/pyclasses/vttlcache.rs +3 -1
  21. cachebox-6.2.7/tests/__init__.py +0 -0
  22. {cachebox-6.2.6 → cachebox-6.2.7}/tests/mixins.py +82 -0
  23. {cachebox-6.2.6 → cachebox-6.2.7}/typos.toml +1 -7
  24. {cachebox-6.2.6 → cachebox-6.2.7}/.github/dependabot.yaml +0 -0
  25. {cachebox-6.2.6 → cachebox-6.2.7}/.github/workflows/docs.yml +0 -0
  26. {cachebox-6.2.6 → cachebox-6.2.7}/.github/workflows/python-test.yml +0 -0
  27. {cachebox-6.2.6 → cachebox-6.2.7}/.gitignore +0 -0
  28. {cachebox-6.2.6 → cachebox-6.2.7}/LICENSE +0 -0
  29. {cachebox-6.2.6 → cachebox-6.2.7}/LICENSE-THIRD-PARTY +0 -0
  30. {cachebox-6.2.6 → cachebox-6.2.7}/README.md +0 -0
  31. {cachebox-6.2.6 → cachebox-6.2.7}/build.rs +0 -0
  32. {cachebox-6.2.6 → cachebox-6.2.7}/cachebox/__init__.py +0 -0
  33. {cachebox-6.2.6 → cachebox-6.2.7}/cachebox/_cachebox.py +0 -0
  34. {cachebox-6.2.6 → cachebox-6.2.7}/cachebox/_core.pyi +0 -0
  35. {cachebox-6.2.6 → cachebox-6.2.7}/cachebox/_wrappers.py +0 -0
  36. /cachebox-6.2.6/tests/__init__.py → /cachebox-6.2.7/cachebox/py.typed +0 -0
  37. {cachebox-6.2.6 → cachebox-6.2.7}/cachebox/utils.py +0 -0
  38. {cachebox-6.2.6 → cachebox-6.2.7}/docs/docs/algorithms.md +0 -0
  39. {cachebox-6.2.6 → cachebox-6.2.7}/docs/docs/api/impls.md +0 -0
  40. {cachebox-6.2.6 → cachebox-6.2.7}/docs/docs/api/index.md +0 -0
  41. {cachebox-6.2.6 → cachebox-6.2.7}/docs/docs/api/utils.md +0 -0
  42. {cachebox-6.2.6 → cachebox-6.2.7}/docs/docs/faq.md +0 -0
  43. {cachebox-6.2.6 → cachebox-6.2.7}/docs/docs/getting-started.md +0 -0
  44. {cachebox-6.2.6 → cachebox-6.2.7}/docs/docs/index.md +0 -0
  45. {cachebox-6.2.6 → cachebox-6.2.7}/docs/docs/installation.md +0 -0
  46. {cachebox-6.2.6 → cachebox-6.2.7}/docs/docs/migration.md +0 -0
  47. {cachebox-6.2.6 → cachebox-6.2.7}/docs/docs/tips.md +0 -0
  48. {cachebox-6.2.6 → cachebox-6.2.7}/docs/mkdocs.yml +0 -0
  49. {cachebox-6.2.6 → cachebox-6.2.7}/pyproject.toml +0 -0
  50. {cachebox-6.2.6 → cachebox-6.2.7}/rust-toolchain.toml +0 -0
  51. {cachebox-6.2.6 → cachebox-6.2.7}/rustfmt.toml +0 -0
  52. {cachebox-6.2.6 → cachebox-6.2.7}/src/hashbrown/alloc.rs +0 -0
  53. {cachebox-6.2.6 → cachebox-6.2.7}/src/hashbrown/control/bitmask.rs +0 -0
  54. {cachebox-6.2.6 → cachebox-6.2.7}/src/hashbrown/control/group/generic.rs +0 -0
  55. {cachebox-6.2.6 → cachebox-6.2.7}/src/hashbrown/control/group/lsx.rs +0 -0
  56. {cachebox-6.2.6 → cachebox-6.2.7}/src/hashbrown/control/group/mod.rs +0 -0
  57. {cachebox-6.2.6 → cachebox-6.2.7}/src/hashbrown/control/group/neon.rs +0 -0
  58. {cachebox-6.2.6 → cachebox-6.2.7}/src/hashbrown/control/group/sse2.rs +0 -0
  59. {cachebox-6.2.6 → cachebox-6.2.7}/src/hashbrown/control/mod.rs +0 -0
  60. {cachebox-6.2.6 → cachebox-6.2.7}/src/hashbrown/control/tag.rs +0 -0
  61. {cachebox-6.2.6 → cachebox-6.2.7}/src/hashbrown/mod.rs +0 -0
  62. {cachebox-6.2.6 → cachebox-6.2.7}/src/hashbrown/raw.rs +0 -0
  63. {cachebox-6.2.6 → cachebox-6.2.7}/src/hashbrown/scopeguard.rs +0 -0
  64. {cachebox-6.2.6 → cachebox-6.2.7}/src/hashbrown/util.rs +0 -0
  65. {cachebox-6.2.6 → cachebox-6.2.7}/src/internal/alias.rs +0 -0
  66. {cachebox-6.2.6 → cachebox-6.2.7}/src/internal/lazyheap.rs +0 -0
  67. {cachebox-6.2.6 → cachebox-6.2.7}/src/internal/linked_list.rs +0 -0
  68. {cachebox-6.2.6 → cachebox-6.2.7}/src/internal/mod.rs +0 -0
  69. {cachebox-6.2.6 → cachebox-6.2.7}/src/internal/onceinit.rs +0 -0
  70. {cachebox-6.2.6 → cachebox-6.2.7}/src/internal/pickle.rs +0 -0
  71. {cachebox-6.2.6 → cachebox-6.2.7}/src/internal/utils.rs +0 -0
  72. {cachebox-6.2.6 → cachebox-6.2.7}/src/lib.rs +0 -0
  73. {cachebox-6.2.6 → cachebox-6.2.7}/src/macro_rules.rs +0 -0
  74. {cachebox-6.2.6 → cachebox-6.2.7}/src/policies/common.rs +0 -0
  75. {cachebox-6.2.6 → cachebox-6.2.7}/src/policies/mod.rs +0 -0
  76. {cachebox-6.2.6 → cachebox-6.2.7}/src/pyclasses/base.rs +0 -0
  77. {cachebox-6.2.6 → cachebox-6.2.7}/src/pyclasses/mod.rs +0 -0
  78. {cachebox-6.2.6 → cachebox-6.2.7}/src/typeref.rs +0 -0
  79. {cachebox-6.2.6 → cachebox-6.2.7}/tests/conftest.py +0 -0
  80. {cachebox-6.2.6 → cachebox-6.2.7}/tests/test_impls.py +0 -0
  81. {cachebox-6.2.6 → cachebox-6.2.7}/tests/test_utils.py +0 -0
  82. {cachebox-6.2.6 → cachebox-6.2.7}/uv.lock +0 -0
@@ -58,21 +58,24 @@ jobs:
58
58
  strategy:
59
59
  matrix:
60
60
  platform:
61
- - { target: x86_64, interpreter: *interpreters }
62
- - { target: x86, interpreter: *interpreters }
63
- - { target: aarch64, interpreter: *interpreters }
64
- - { target: armv7, interpreter: *interpreters }
65
- - { target: s390x, interpreter: *interpreters }
66
- - { target: ppc64le, interpreter: *interpreters }
61
+ - { target: x86_64-unknown-linux-gnu, arch: x86_64, interpreter: *interpreters }
62
+ - { target: i686-unknown-linux-gnu, arch: x86, interpreter: *interpreters }
63
+ - { target: aarch64-unknown-linux-gnu, arch: aarch64, interpreter: *interpreters }
64
+ - { target: armv7-unknown-linux-gnueabihf, arch: armv7, interpreter: *interpreters }
65
+ - { target: s390x-unknown-linux-gnu, arch: s390x, interpreter: *interpreters }
66
+ - { target: powerpc64le-unknown-linux-gnu, arch: ppc64le, interpreter: *interpreters }
67
+ - { target: riscv64gc-unknown-linux-gnu, arch: riscv64, interpreter: *interpreters }
67
68
 
68
69
  steps:
69
70
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
70
71
  with:
71
72
  persist-credentials: false
72
73
 
73
- - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
74
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
74
75
  with:
75
- python-version: "3.x"
76
+ activate-environment: true
77
+ enable-cache: false
78
+ python-version: "3"
76
79
 
77
80
  - uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
78
81
  # zizmor: ignore[cache-poisoning]
@@ -88,7 +91,7 @@ jobs:
88
91
 
89
92
  - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
90
93
  with:
91
- name: wheels-linux-${{ matrix.platform.target }}
94
+ name: wheels-linux-${{ matrix.platform.arch }}
92
95
  path: dist
93
96
 
94
97
  build-musllinux:
@@ -96,19 +99,22 @@ jobs:
96
99
  strategy:
97
100
  matrix:
98
101
  platform:
99
- - { target: x86_64, interpreter: *interpreters }
100
- - { target: x86, interpreter: *interpreters }
101
- - { target: aarch64, interpreter: *interpreters }
102
- - { target: armv7, interpreter: *interpreters }
102
+ - { target: x86_64-unknown-linux-musl, arch: x86_64, interpreter: *interpreters }
103
+ - { target: i686-unknown-linux-musl, arch: x86, interpreter: *interpreters }
104
+ - { target: aarch64-unknown-linux-musl, arch: aarch64, interpreter: *interpreters }
105
+ - { target: armv7-unknown-linux-musleabihf, arch: armv7, interpreter: *interpreters }
106
+ - { target: riscv64gc-unknown-linux-musl, arch: riscv64, interpreter: *interpreters }
103
107
 
104
108
  steps:
105
109
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
106
110
  with:
107
111
  persist-credentials: false
108
112
 
109
- - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
113
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
110
114
  with:
111
- python-version: "3.x"
115
+ activate-environment: true
116
+ enable-cache: false
117
+ python-version: "3"
112
118
 
113
119
  - uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
114
120
  # zizmor: ignore[cache-poisoning]
@@ -124,7 +130,7 @@ jobs:
124
130
 
125
131
  - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
126
132
  with:
127
- name: wheels-musllinux-${{ matrix.platform.target }}
133
+ name: wheels-musllinux-${{ matrix.platform.arch }}
128
134
  path: dist
129
135
 
130
136
  build-windows:
@@ -37,7 +37,7 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
37
37
 
38
38
  [[package]]
39
39
  name = "cachebox"
40
- version = "6.2.6"
40
+ version = "6.2.7"
41
41
  dependencies = [
42
42
  "allocator-api2",
43
43
  "cfg-if",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "cachebox"
3
- version = "6.2.6"
3
+ version = "6.2.7"
4
4
  edition = "2021"
5
5
  description = "The fastest memoizing and caching Python library written in Rust"
6
6
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cachebox
3
- Version: 6.2.6
3
+ Version: 6.2.7
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python
6
6
  Classifier: Programming Language :: Python :: 3
@@ -419,13 +419,13 @@ impl PolicyExt for FIFOPolicy {
419
419
  maxsize: usize,
420
420
  getsizeof: Option<alias::PyObject>,
421
421
  _global_ttl: Option<std::time::Duration>,
422
- builded: pyo3::Bound<'_, pyo3::types::PyTuple>,
422
+ built: pyo3::Bound<'_, pyo3::types::PyTuple>,
423
423
  ) -> pyo3::PyResult<(Self::Shared, Self)> {
424
424
  use pyo3::types::PyAnyMethods;
425
425
  use pyo3::types::PyListMethods;
426
426
  use pyo3::types::PyTupleMethods;
427
427
 
428
- let list = builded.get_item(0)?.cast_into::<pyo3::types::PyList>()?;
428
+ let list = built.get_item(0)?.cast_into::<pyo3::types::PyList>()?;
429
429
  let list_length = list.len();
430
430
 
431
431
  if list_length > maxsize {
@@ -501,13 +501,13 @@ impl PolicyExt for LFUPolicy {
501
501
  maxsize: usize,
502
502
  getsizeof: Option<alias::PyObject>,
503
503
  _global_ttl: Option<std::time::Duration>,
504
- builded: pyo3::Bound<'_, pyo3::types::PyTuple>,
504
+ built: pyo3::Bound<'_, pyo3::types::PyTuple>,
505
505
  ) -> pyo3::PyResult<(Self::Shared, Self)> {
506
506
  use pyo3::types::PyAnyMethods;
507
507
  use pyo3::types::PyListMethods;
508
508
  use pyo3::types::PyTupleMethods;
509
509
 
510
- let list = builded.get_item(0)?.cast_into::<pyo3::types::PyList>()?;
510
+ let list = built.get_item(0)?.cast_into::<pyo3::types::PyList>()?;
511
511
  let list_length = list.len();
512
512
 
513
513
  if list_length > maxsize {
@@ -347,13 +347,13 @@ impl PolicyExt for LRUPolicy {
347
347
  maxsize: usize,
348
348
  getsizeof: Option<alias::PyObject>,
349
349
  _global_ttl: Option<std::time::Duration>,
350
- builded: pyo3::Bound<'_, pyo3::types::PyTuple>,
350
+ built: pyo3::Bound<'_, pyo3::types::PyTuple>,
351
351
  ) -> pyo3::PyResult<(Self::Shared, Self)> {
352
352
  use pyo3::types::PyAnyMethods;
353
353
  use pyo3::types::PyListMethods;
354
354
  use pyo3::types::PyTupleMethods;
355
355
 
356
- let list = builded.get_item(0)?.cast_into::<pyo3::types::PyList>()?;
356
+ let list = built.get_item(0)?.cast_into::<pyo3::types::PyList>()?;
357
357
  let list_length = list.len();
358
358
 
359
359
  if list_length > maxsize {
@@ -266,12 +266,12 @@ impl PolicyExt for NoPolicy {
266
266
  maxsize: usize,
267
267
  getsizeof: Option<alias::PyObject>,
268
268
  _global_ttl: Option<std::time::Duration>,
269
- builded: pyo3::Bound<'_, pyo3::types::PyTuple>,
269
+ built: pyo3::Bound<'_, pyo3::types::PyTuple>,
270
270
  ) -> pyo3::PyResult<(Self::Shared, Self)> {
271
271
  use pyo3::types::PyDictMethods;
272
272
  use pyo3::types::PyTupleMethods;
273
273
 
274
- let dict = builded.get_item(0)?.cast_into::<pyo3::types::PyDict>()?;
274
+ let dict = built.get_item(0)?.cast_into::<pyo3::types::PyDict>()?;
275
275
  let dict_length = dict.len();
276
276
 
277
277
  if dict_length > maxsize {
@@ -277,12 +277,12 @@ impl PolicyExt for RRPolicy {
277
277
  maxsize: usize,
278
278
  getsizeof: Option<alias::PyObject>,
279
279
  _global_ttl: Option<std::time::Duration>,
280
- builded: pyo3::Bound<'_, pyo3::types::PyTuple>,
280
+ built: pyo3::Bound<'_, pyo3::types::PyTuple>,
281
281
  ) -> pyo3::PyResult<(Self::Shared, Self)> {
282
282
  use pyo3::types::PyDictMethods;
283
283
  use pyo3::types::PyTupleMethods;
284
284
 
285
- let dict = builded.get_item(0)?.cast_into::<pyo3::types::PyDict>()?;
285
+ let dict = built.get_item(0)?.cast_into::<pyo3::types::PyDict>()?;
286
286
  let dict_length = dict.len();
287
287
 
288
288
  if dict_length > maxsize {
@@ -152,6 +152,6 @@ pub trait PolicyExt: Sized {
152
152
  maxsize: usize,
153
153
  getsizeof: Option<alias::PyObject>,
154
154
  global_ttl: Option<std::time::Duration>,
155
- builded: pyo3::Bound<'_, pyo3::types::PyTuple>,
155
+ built: pyo3::Bound<'_, pyo3::types::PyTuple>,
156
156
  ) -> pyo3::PyResult<(Self::Shared, Self)>;
157
157
  }
@@ -556,7 +556,7 @@ impl PolicyExt for TTLPolicy {
556
556
  maxsize: usize,
557
557
  getsizeof: Option<alias::PyObject>,
558
558
  global_ttl: Option<std::time::Duration>,
559
- builded: pyo3::Bound<'_, pyo3::types::PyTuple>,
559
+ built: pyo3::Bound<'_, pyo3::types::PyTuple>,
560
560
  ) -> pyo3::PyResult<(Self::Shared, Self)> {
561
561
  use pyo3::types::PyAnyMethods;
562
562
  use pyo3::types::PyListMethods;
@@ -566,7 +566,7 @@ impl PolicyExt for TTLPolicy {
566
566
  return Err(new_py_error!(PyValueError, "global_ttl is zero"));
567
567
  }
568
568
 
569
- let list = builded.get_item(0)?.cast_into::<pyo3::types::PyList>()?;
569
+ let list = built.get_item(0)?.cast_into::<pyo3::types::PyList>()?;
570
570
  let list_length = list.len();
571
571
 
572
572
  if list_length > maxsize {
@@ -505,13 +505,13 @@ impl PolicyExt for VTTLPolicy {
505
505
  maxsize: usize,
506
506
  getsizeof: Option<alias::PyObject>,
507
507
  _global_ttl: Option<std::time::Duration>,
508
- builded: pyo3::Bound<'_, pyo3::types::PyTuple>,
508
+ built: pyo3::Bound<'_, pyo3::types::PyTuple>,
509
509
  ) -> pyo3::PyResult<(Self::Shared, Self)> {
510
510
  use pyo3::types::PyAnyMethods;
511
511
  use pyo3::types::PyListMethods;
512
512
  use pyo3::types::PyTupleMethods;
513
513
 
514
- let list = builded.get_item(0)?.cast_into::<pyo3::types::PyList>()?;
514
+ let list = built.get_item(0)?.cast_into::<pyo3::types::PyList>()?;
515
515
  let list_length = list.len();
516
516
 
517
517
  if list_length > maxsize {
@@ -53,6 +53,16 @@ impl<P: PolicyExt> Wrapped<P> {
53
53
  pub fn policy(&self) -> parking_lot::MutexGuard<'_, P> {
54
54
  self.inner.lock()
55
55
  }
56
+
57
+ /// Acquires the mutex only if it is free, returning `None` otherwise.
58
+ ///
59
+ /// For callers that must never wait for the lock, such as `__traverse__`:
60
+ /// the thread holding the lock may be running Python code, and a garbage
61
+ /// collection pass landing there would deadlock the whole process.
62
+ #[inline(always)]
63
+ pub fn try_policy(&self) -> Option<parking_lot::MutexGuard<'_, P>> {
64
+ self.inner.try_lock()
65
+ }
56
66
  }
57
67
 
58
68
  #[inline(always)]
@@ -298,13 +308,13 @@ impl<P: PolicyExt> Wrapped<P> {
298
308
  return Err(new_py_error!(PyValueError, "global_ttl is negative"));
299
309
  }
300
310
 
301
- let builded = tuple.get_item(3)?.cast_into::<pyo3::types::PyTuple>()?;
311
+ let built = tuple.get_item(3)?.cast_into::<pyo3::types::PyTuple>()?;
302
312
 
303
313
  let (shared, inner) = P::from_pickle(
304
314
  maxsize,
305
315
  getsizeof,
306
316
  global_ttl.map(std::time::Duration::from_secs_f64),
307
- builded,
317
+ built,
308
318
  )?;
309
319
 
310
320
  Ok(Self {
@@ -651,7 +651,9 @@ impl PyCache {
651
651
  }
652
652
 
653
653
  let inner = self.0.get();
654
- let policy = inner.policy();
654
+ let Some(policy) = inner.try_policy() else {
655
+ return Ok(());
656
+ };
655
657
 
656
658
  for handle_ref in unsafe { policy.table().iter() } {
657
659
  let handle = unsafe { handle_ref.as_ref() };
@@ -679,7 +679,9 @@ impl PyFIFOCache {
679
679
  }
680
680
 
681
681
  let inner = self.0.get();
682
- let policy = inner.policy();
682
+ let Some(policy) = inner.try_policy() else {
683
+ return Ok(());
684
+ };
683
685
 
684
686
  for handle in policy.entries().iter() {
685
687
  visit.call(handle.key().as_ref())?;
@@ -744,7 +744,9 @@ impl PyLFUCache {
744
744
  }
745
745
 
746
746
  let inner = self.0.get();
747
- let policy = inner.policy();
747
+ let Some(policy) = inner.try_policy() else {
748
+ return Ok(());
749
+ };
748
750
 
749
751
  for cursor in unsafe { policy.table().iter() } {
750
752
  let handle = unsafe { cursor.as_ref().element() };
@@ -725,7 +725,9 @@ impl PyLRUCache {
725
725
  }
726
726
 
727
727
  let inner = self.0.get();
728
- let policy = inner.policy();
728
+ let Some(policy) = inner.try_policy() else {
729
+ return Ok(());
730
+ };
729
731
 
730
732
  for cursor in unsafe { policy.list().iter() } {
731
733
  let handle = unsafe { cursor.element() };
@@ -671,7 +671,9 @@ impl PyRRCache {
671
671
  }
672
672
 
673
673
  let inner = self.0.get();
674
- let policy = inner.policy();
674
+ let Some(policy) = inner.try_policy() else {
675
+ return Ok(());
676
+ };
675
677
 
676
678
  for handle_ref in unsafe { policy.table().iter() } {
677
679
  let handle = unsafe { handle_ref.as_ref() };
@@ -816,7 +816,9 @@ impl PyTTLCache {
816
816
  }
817
817
 
818
818
  let inner = self.0.get();
819
- let policy = inner.policy();
819
+ let Some(policy) = inner.try_policy() else {
820
+ return Ok(());
821
+ };
820
822
 
821
823
  for handle in policy.entries().iter() {
822
824
  visit.call(handle.key().as_ref())?;
@@ -776,7 +776,9 @@ impl PyVTTLCache {
776
776
  }
777
777
 
778
778
  let inner = self.0.get();
779
- let policy = inner.policy();
779
+ let Some(policy) = inner.try_policy() else {
780
+ return Ok(());
781
+ };
780
782
 
781
783
  for cursor in unsafe { policy.table().iter() } {
782
784
  let handle = unsafe { cursor.as_ref().element() };
File without changes
@@ -101,6 +101,23 @@ class InitializeMixin(BaseMixin):
101
101
 
102
102
 
103
103
  class InsertAndGetMixin(BaseMixin):
104
+ def test_key_eq_may_trigger_the_gc(self):
105
+ # __eq__ runs in the middle of a probe, with the lock held; a deadlock
106
+ # here would keep the GIL, so the call runs in a child process
107
+ name = type(self.create_cache()).__name__
108
+
109
+ try:
110
+ done = subprocess.run(
111
+ [sys.executable, "-c", EQ_TRIGGERING_GC, name],
112
+ capture_output=True,
113
+ text=True,
114
+ timeout=60,
115
+ )
116
+ except subprocess.TimeoutExpired:
117
+ pytest.fail(f"{name}.get() with a colliding key never returned")
118
+
119
+ assert done.stdout.strip() == "ok", done.stderr
120
+
104
121
  def test_insert_returns_none_on_new_key(self):
105
122
  cache = self.create_cache()
106
123
 
@@ -205,6 +222,55 @@ class SetDefaultMixin(BaseMixin):
205
222
  assert cache.get("k") == "existing"
206
223
 
207
224
 
225
+ DROPPED_VALUE_TRIGGERING_GC = """
226
+ import gc
227
+ import sys
228
+
229
+ import cachebox
230
+
231
+ name = sys.argv[1]
232
+ cls = getattr(cachebox, name)
233
+ cache = cls(10, global_ttl=60) if name == "TTLCache" else cls(10)
234
+
235
+
236
+ class Boom:
237
+ def __del__(self):
238
+ gc.collect()
239
+
240
+
241
+ cache.insert("k", Boom())
242
+ cache.clear()
243
+ print("ok")
244
+ """
245
+
246
+ EQ_TRIGGERING_GC = """
247
+ import gc
248
+ import sys
249
+
250
+ import cachebox
251
+
252
+ name = sys.argv[1]
253
+ cls = getattr(cachebox, name)
254
+ cache = cls(10, global_ttl=60) if name == "TTLCache" else cls(10)
255
+
256
+
257
+ class Key:
258
+ def __init__(self, name):
259
+ self.name = name
260
+
261
+ def __hash__(self):
262
+ return 42 # same hash for every key, so lookups have to call __eq__
263
+
264
+ def __eq__(self, other):
265
+ gc.collect()
266
+ return self.name == other.name
267
+
268
+
269
+ cache.insert(Key("a"), 1)
270
+ assert cache.get(Key("b")) is None
271
+ print("ok")
272
+ """
273
+
208
274
  FACTORY_TOUCHING_CACHE = """
209
275
  import gc
210
276
  import sys
@@ -635,6 +701,22 @@ class IterationMixin(BaseMixin):
635
701
 
636
702
 
637
703
  class DrainClearShrinkMixin(BaseMixin):
704
+ def test_dropping_a_value_may_trigger_the_gc(self):
705
+ # a deadlock here would keep the GIL, so the call runs in a child process
706
+ name = type(self.create_cache()).__name__
707
+
708
+ try:
709
+ done = subprocess.run(
710
+ [sys.executable, "-c", DROPPED_VALUE_TRIGGERING_GC, name],
711
+ capture_output=True,
712
+ text=True,
713
+ timeout=60,
714
+ )
715
+ except subprocess.TimeoutExpired:
716
+ pytest.fail(f"{name}.clear() never returned")
717
+
718
+ assert done.stdout.strip() == "ok", done.stderr
719
+
638
720
  def test_clear_removes_all_items(self):
639
721
  cache = self.create_cache()
640
722
 
@@ -1,13 +1,7 @@
1
1
  [default.extend-words]
2
- # error: `builded` should be `built`
3
- # --> .\src\policies\ttlpolicy.rs:569:20
4
- # |
5
- # 569 | let list = builded.get_item(0)?.cast_into::<pyo3::types::PyList>()?;
6
- # | ^^^^^^^
7
- builded = "builded"
8
2
  # error: `fo` should be `of`, `for`, `do`, `go`, `to`
9
3
  # --> .\src\policies\fifopolicy.rs:212:66
10
4
  # |
11
5
  # 212 | // - All others: (vd_idx + fo) - fo = vd_idx
12
6
  # | ^^
13
- fo = "fo"
7
+ fo = "fo"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes