cachebox 6.2.0__tar.gz → 6.2.2__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.
- {cachebox-6.2.0 → cachebox-6.2.2}/.github/workflows/CI.yml +4 -6
- {cachebox-6.2.0 → cachebox-6.2.2}/.github/workflows/python-test.yml +12 -9
- {cachebox-6.2.0 → cachebox-6.2.2}/Cargo.lock +8 -1
- {cachebox-6.2.0 → cachebox-6.2.2}/Cargo.toml +2 -2
- {cachebox-6.2.0 → cachebox-6.2.2}/PKG-INFO +2 -1
- {cachebox-6.2.0 → cachebox-6.2.2}/README.md +1 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/cachebox/_wrappers.py +19 -35
- {cachebox-6.2.0 → cachebox-6.2.2}/cachebox/utils.py +34 -9
- {cachebox-6.2.0 → cachebox-6.2.2}/requirements-dev.txt +1 -0
- cachebox-6.2.2/src/hashbrown/alloc.rs +21 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/hashbrown/raw.rs +4 -5
- cachebox-6.2.2/src/hashbrown/util.rs +23 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/internal/lazyheap.rs +1 -1
- {cachebox-6.2.0 → cachebox-6.2.2}/src/internal/linked_list.rs +1 -1
- {cachebox-6.2.0 → cachebox-6.2.2}/src/internal/onceinit.rs +1 -1
- {cachebox-6.2.0 → cachebox-6.2.2}/src/internal/utils.rs +1 -1
- {cachebox-6.2.0 → cachebox-6.2.2}/src/lib.rs +4 -8
- {cachebox-6.2.0 → cachebox-6.2.2}/src/policies/fifopolicy.rs +2 -1
- {cachebox-6.2.0 → cachebox-6.2.2}/src/policies/ttlpolicy.rs +4 -7
- {cachebox-6.2.0 → cachebox-6.2.2}/tests/test_utils.py +124 -3
- cachebox-6.2.0/src/hashbrown/alloc.rs +0 -13
- cachebox-6.2.0/src/hashbrown/util.rs +0 -9
- {cachebox-6.2.0 → cachebox-6.2.2}/.github/workflows/docs.yml +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/.gitignore +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/LICENSE +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/LICENSE-THIRD-PARTY +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/build.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/cachebox/__init__.py +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/cachebox/_cachebox.py +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/cachebox/_core.pyi +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/docs/docs/api/impls.md +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/docs/docs/api/index.md +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/docs/docs/api/utils.md +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/docs/docs/getting-started.md +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/docs/docs/index.md +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/docs/docs/installation.md +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/docs/docs/migration.md +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/docs/docs/tips.md +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/docs/mkdocs.yml +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/pyproject.toml +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/rustfmt.toml +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/hashbrown/control/bitmask.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/hashbrown/control/group/generic.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/hashbrown/control/group/lsx.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/hashbrown/control/group/mod.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/hashbrown/control/group/neon.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/hashbrown/control/group/sse2.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/hashbrown/control/mod.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/hashbrown/control/tag.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/hashbrown/mod.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/hashbrown/scopeguard.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/internal/alias.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/internal/mod.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/internal/pickle.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/macro_rules.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/policies/common.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/policies/lfupolicy.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/policies/lrupolicy.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/policies/mod.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/policies/nopolicy.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/policies/rrpolicy.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/policies/traits.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/policies/vttlpolicy.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/policies/wrapped.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/pyclasses/base.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/pyclasses/cache.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/pyclasses/fifocache.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/pyclasses/lfucache.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/pyclasses/lrucache.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/pyclasses/mod.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/pyclasses/rrcache.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/pyclasses/ttlcache.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/pyclasses/vttlcache.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/src/typeref.rs +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/tests/__init__.py +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/tests/conftest.py +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/tests/mixins.py +0 -0
- {cachebox-6.2.0 → cachebox-6.2.2}/tests/test_impls.py +0 -0
|
@@ -57,7 +57,6 @@ jobs:
|
|
|
57
57
|
args: --release --out dist --interpreter ${{ matrix.platform.interpreter }}
|
|
58
58
|
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
59
59
|
manylinux: auto
|
|
60
|
-
rust-toolchain: nightly
|
|
61
60
|
|
|
62
61
|
- uses: actions/upload-artifact@v7.0.1
|
|
63
62
|
with:
|
|
@@ -101,7 +100,6 @@ jobs:
|
|
|
101
100
|
args: --release --out dist --interpreter ${{ matrix.platform.interpreter }}
|
|
102
101
|
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
103
102
|
manylinux: musllinux_1_2
|
|
104
|
-
rust-toolchain: nightly
|
|
105
103
|
|
|
106
104
|
- uses: actions/upload-artifact@v7.0.1
|
|
107
105
|
with:
|
|
@@ -117,7 +115,10 @@ jobs:
|
|
|
117
115
|
target: x64,
|
|
118
116
|
interpreter: "3.10 3.11 3.12 3.13 3.14 3.14t 3.15 3.15t pypy3.11",
|
|
119
117
|
}
|
|
120
|
-
- {
|
|
118
|
+
- {
|
|
119
|
+
target: x86,
|
|
120
|
+
interpreter: "3.10 3.11 3.12 3.13 3.14 3.14t 3.15 3.15t",
|
|
121
|
+
}
|
|
121
122
|
|
|
122
123
|
steps:
|
|
123
124
|
- uses: actions/checkout@v6.0.2
|
|
@@ -134,7 +135,6 @@ jobs:
|
|
|
134
135
|
target: ${{ matrix.platform.target }}
|
|
135
136
|
args: --release --out dist --interpreter ${{ matrix.platform.interpreter }}
|
|
136
137
|
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
137
|
-
rust-toolchain: nightly
|
|
138
138
|
|
|
139
139
|
- uses: actions/upload-artifact@v7.0.1
|
|
140
140
|
with:
|
|
@@ -169,7 +169,6 @@ jobs:
|
|
|
169
169
|
target: ${{ matrix.platform.target }}
|
|
170
170
|
args: --release --out dist --interpreter ${{ matrix.platform.interpreter }}
|
|
171
171
|
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
172
|
-
rust-toolchain: nightly
|
|
173
172
|
|
|
174
173
|
- uses: actions/upload-artifact@v7.0.1
|
|
175
174
|
with:
|
|
@@ -188,7 +187,6 @@ jobs:
|
|
|
188
187
|
with:
|
|
189
188
|
command: sdist
|
|
190
189
|
args: --out dist
|
|
191
|
-
rust-toolchain: nightly
|
|
192
190
|
|
|
193
191
|
- name: Upload sdist
|
|
194
192
|
uses: actions/upload-artifact@v7.0.1
|
|
@@ -16,7 +16,7 @@ env:
|
|
|
16
16
|
|
|
17
17
|
jobs:
|
|
18
18
|
test-python:
|
|
19
|
-
name:
|
|
19
|
+
name: ${{ matrix.python-version }}
|
|
20
20
|
runs-on: ubuntu-latest
|
|
21
21
|
continue-on-error: ${{ endsWith(matrix.python-version, 't') }}
|
|
22
22
|
strategy:
|
|
@@ -36,22 +36,24 @@ jobs:
|
|
|
36
36
|
steps:
|
|
37
37
|
- uses: actions/checkout@v6.0.2
|
|
38
38
|
|
|
39
|
-
- uses: dtolnay/rust-toolchain@
|
|
39
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
40
40
|
|
|
41
41
|
- uses: actions/setup-python@v6.2.0
|
|
42
42
|
with:
|
|
43
43
|
python-version: ${{ matrix.python-version }}
|
|
44
44
|
allow-prereleases: true
|
|
45
45
|
|
|
46
|
-
- run: pip install virtualenv
|
|
46
|
+
- run: pip install virtualenv
|
|
47
|
+
- run: pip install -r requirements-dev.txt
|
|
47
48
|
- run: virtualenv --no-vcs-ignore .venv
|
|
49
|
+
|
|
48
50
|
- run: maturin develop --features use-small-offset
|
|
49
|
-
- run: pytest -v
|
|
51
|
+
- run: pytest -v -n auto
|
|
50
52
|
env:
|
|
51
53
|
HYPOTHESIS_PROFILE: slow
|
|
52
54
|
|
|
53
55
|
test-os:
|
|
54
|
-
name:
|
|
56
|
+
name: ${{ matrix.os }}
|
|
55
57
|
runs-on: ${{ matrix.os }}-latest
|
|
56
58
|
strategy:
|
|
57
59
|
fail-fast: false
|
|
@@ -61,16 +63,17 @@ jobs:
|
|
|
61
63
|
steps:
|
|
62
64
|
- uses: actions/checkout@v6.0.2
|
|
63
65
|
|
|
64
|
-
- uses: dtolnay/rust-toolchain@
|
|
66
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
65
67
|
|
|
66
68
|
- uses: actions/setup-python@v6.2.0
|
|
67
69
|
with:
|
|
68
70
|
python-version: "3.13"
|
|
69
|
-
allow-prereleases: true
|
|
70
71
|
|
|
71
|
-
- run: pip install virtualenv
|
|
72
|
+
- run: pip install virtualenv
|
|
73
|
+
- run: pip install -r requirements-dev.txt
|
|
72
74
|
- run: virtualenv --no-vcs-ignore .venv
|
|
75
|
+
|
|
73
76
|
- run: maturin develop --features use-small-offset
|
|
74
|
-
- run: pytest -v
|
|
77
|
+
- run: pytest -v -n auto
|
|
75
78
|
env:
|
|
76
79
|
HYPOTHESIS_PROFILE: slow
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
# It is not intended for manual editing.
|
|
3
3
|
version = 4
|
|
4
4
|
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "allocator-api2"
|
|
7
|
+
version = "0.4.0"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "c880a97d28a3681c0267bd29cff89621202715b065127cd445fa0f0fe0aa2880"
|
|
10
|
+
|
|
5
11
|
[[package]]
|
|
6
12
|
name = "android_system_properties"
|
|
7
13
|
version = "0.1.5"
|
|
@@ -31,8 +37,9 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
|
|
31
37
|
|
|
32
38
|
[[package]]
|
|
33
39
|
name = "cachebox"
|
|
34
|
-
version = "6.2.
|
|
40
|
+
version = "6.2.2"
|
|
35
41
|
dependencies = [
|
|
42
|
+
"allocator-api2",
|
|
36
43
|
"cfg-if",
|
|
37
44
|
"chrono",
|
|
38
45
|
"fastrand",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "cachebox"
|
|
3
|
-
version = "6.2.
|
|
3
|
+
version = "6.2.2"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
description = "The fastest memoizing and caching Python library written in Rust"
|
|
6
6
|
readme = "README.md"
|
|
@@ -22,12 +22,12 @@ panic = "abort"
|
|
|
22
22
|
strip = true
|
|
23
23
|
|
|
24
24
|
[dependencies]
|
|
25
|
+
allocator-api2 = "0.4.0"
|
|
25
26
|
cfg-if = "1.0.4"
|
|
26
27
|
chrono = "0.4.44"
|
|
27
28
|
fastrand = "2.4.1"
|
|
28
29
|
parking_lot = {version="0.12.5", default-features=false}
|
|
29
30
|
pyo3 = {version="0.29", default-features=false, features=["macros", "chrono"]}
|
|
30
|
-
# tokio = {version="1.52.3", default-features=false, features=["sync"]}
|
|
31
31
|
|
|
32
32
|
[build-dependencies]
|
|
33
33
|
pyo3-build-config = {version="0.29", default-features=false, features=["resolve-config"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cachebox
|
|
3
|
-
Version: 6.2.
|
|
3
|
+
Version: 6.2.2
|
|
4
4
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
5
5
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -41,6 +41,7 @@ Project-URL: Homepage, https://github.com/awolverp/cachebox
|
|
|
41
41
|
|
|
42
42
|
[](https://github.com/awolverp/cachebox/blob/main/LICENSE)
|
|
43
43
|
[](https://pepy.tech/projects/cachebox)
|
|
44
|
+
[](https://payrequest.me/ali-pooralijan-awolverp)
|
|
44
45
|
|
|
45
46
|
</div>
|
|
46
47
|
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
[](https://github.com/awolverp/cachebox/blob/main/LICENSE)
|
|
12
12
|
[](https://pepy.tech/projects/cachebox)
|
|
13
|
+
[](https://payrequest.me/ali-pooralijan-awolverp)
|
|
13
14
|
|
|
14
15
|
</div>
|
|
15
16
|
|
|
@@ -18,8 +18,10 @@ class _Lock:
|
|
|
18
18
|
|
|
19
19
|
def __enter__(self) -> None:
|
|
20
20
|
self.waiters += 1
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
try:
|
|
22
|
+
self._lock.__enter__()
|
|
23
|
+
finally:
|
|
24
|
+
self.waiters -= 1
|
|
23
25
|
|
|
24
26
|
def __exit__(self, *_) -> None:
|
|
25
27
|
self._lock.__exit__(*_)
|
|
@@ -34,8 +36,10 @@ class _AsyncLock:
|
|
|
34
36
|
|
|
35
37
|
async def __aenter__(self) -> None:
|
|
36
38
|
self.waiters += 1
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
try:
|
|
40
|
+
await self._lock.__aenter__()
|
|
41
|
+
finally:
|
|
42
|
+
self.waiters -= 1
|
|
39
43
|
|
|
40
44
|
async def __aexit__(self, *_) -> None:
|
|
41
45
|
await self._lock.__aexit__(*_)
|
|
@@ -102,7 +106,7 @@ def _cached_wrapper_without_lock(
|
|
|
102
106
|
|
|
103
107
|
result = func(*args, **kwds)
|
|
104
108
|
_cache.insert(key, result)
|
|
105
|
-
|
|
109
|
+
misses += 1
|
|
106
110
|
if callback is not None:
|
|
107
111
|
callback(EVENT_MISS, key, result)
|
|
108
112
|
|
|
@@ -159,7 +163,7 @@ def _async_cached_wrapper_without_lock(
|
|
|
159
163
|
# Passing `cachebox__ignore=True` bypasses the cache and
|
|
160
164
|
# calls the function directly.
|
|
161
165
|
if kwds.pop("cachebox__ignore", False):
|
|
162
|
-
return func(*args, **kwds)
|
|
166
|
+
return await func(*args, **kwds)
|
|
163
167
|
|
|
164
168
|
_cache: BaseCacheImpl = cache(args[0]) if cache_is_fn else cache # type: ignore[arg-type]
|
|
165
169
|
key = _make_key(args, kwds)
|
|
@@ -266,17 +270,19 @@ def _cached_wrapper(
|
|
|
266
270
|
except KeyError:
|
|
267
271
|
try:
|
|
268
272
|
result = func(*args, **kwds)
|
|
269
|
-
except
|
|
273
|
+
except BaseException as exc:
|
|
270
274
|
if lock.waiters > 0:
|
|
271
275
|
pending_errors[key] = exc
|
|
276
|
+
|
|
272
277
|
raise
|
|
273
278
|
else:
|
|
274
279
|
_cache[key] = result
|
|
275
280
|
misses += 1
|
|
276
281
|
event = EVENT_MISS
|
|
277
282
|
|
|
278
|
-
|
|
279
|
-
|
|
283
|
+
finally:
|
|
284
|
+
if lock.waiters == 0:
|
|
285
|
+
locks.pop(key, None)
|
|
280
286
|
|
|
281
287
|
if callback is not None:
|
|
282
288
|
callback(event, key, result)
|
|
@@ -319,29 +325,7 @@ def _async_cached_wrapper(
|
|
|
319
325
|
hits = 0
|
|
320
326
|
misses = 0
|
|
321
327
|
|
|
322
|
-
# See _cached_wrapper
|
|
323
328
|
locks: Cache[typing.Hashable, _AsyncLock] = Cache(0)
|
|
324
|
-
|
|
325
|
-
# if lock_type is asyncio.Lock:
|
|
326
|
-
|
|
327
|
-
# async def _get_lock(key: typing.Hashable):
|
|
328
|
-
# return locks.setdefault(key, _AsyncLock(lock_type()))
|
|
329
|
-
|
|
330
|
-
# else:
|
|
331
|
-
# _lock_creation_guard = asyncio.Lock()
|
|
332
|
-
|
|
333
|
-
# async def _get_lock(key: typing.Hashable):
|
|
334
|
-
# lock = locks.get(key)
|
|
335
|
-
# if lock is not None:
|
|
336
|
-
# return lock
|
|
337
|
-
|
|
338
|
-
# async with _lock_creation_guard:
|
|
339
|
-
# lock = locks.get(key)
|
|
340
|
-
# if lock is None:
|
|
341
|
-
# locks[key] = lock = _AsyncLock(lock_type())
|
|
342
|
-
|
|
343
|
-
# return lock
|
|
344
|
-
|
|
345
329
|
pending_errors: dict[typing.Hashable, BaseException] = {}
|
|
346
330
|
|
|
347
331
|
async def _wrapped(*args, **kwds):
|
|
@@ -381,7 +365,7 @@ def _async_cached_wrapper(
|
|
|
381
365
|
except KeyError:
|
|
382
366
|
try:
|
|
383
367
|
result = await func(*args, **kwds)
|
|
384
|
-
except
|
|
368
|
+
except BaseException as exc:
|
|
385
369
|
if lock.waiters > 0:
|
|
386
370
|
pending_errors[key] = exc
|
|
387
371
|
raise
|
|
@@ -389,9 +373,9 @@ def _async_cached_wrapper(
|
|
|
389
373
|
_cache[key] = result
|
|
390
374
|
misses += 1
|
|
391
375
|
event = EVENT_MISS
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
376
|
+
finally:
|
|
377
|
+
if lock.waiters == 0:
|
|
378
|
+
locks.pop(key, None)
|
|
395
379
|
|
|
396
380
|
await _call_async_callback(callback, event, key, result)
|
|
397
381
|
|
|
@@ -73,12 +73,14 @@ def make_key(*args, **kwds) -> typing.Hashable:
|
|
|
73
73
|
if not kwds:
|
|
74
74
|
if len(args) == 1 and type(args[0]) in _FAST_TYPES:
|
|
75
75
|
return args[0]
|
|
76
|
+
|
|
76
77
|
return args
|
|
77
78
|
|
|
78
79
|
key = args + (_KWDS_MARK,)
|
|
79
80
|
for item in kwds.items():
|
|
80
81
|
key += item
|
|
81
|
-
|
|
82
|
+
|
|
83
|
+
return key
|
|
82
84
|
|
|
83
85
|
|
|
84
86
|
def make_hash_key(*args, **kwds) -> int:
|
|
@@ -155,8 +157,13 @@ class Frozen(BaseCacheImpl[KT, VT]): # pragma: no cover
|
|
|
155
157
|
ignore: If ``True``, silently ignores modification attempts; if ``False``, raises
|
|
156
158
|
``TypeError`` when modification is attempted. Default is ``False``.
|
|
157
159
|
"""
|
|
158
|
-
|
|
159
|
-
|
|
160
|
+
if not isinstance(cls, BaseCacheImpl):
|
|
161
|
+
raise TypeError(
|
|
162
|
+
f"expected a BaseCacheImpl instance, got {type(cls).__name__!r}"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if type(cls) is Frozen:
|
|
166
|
+
raise TypeError("cannot wrap an already-frozen cache")
|
|
160
167
|
|
|
161
168
|
self.__cache = cls
|
|
162
169
|
self.ignore = ignore
|
|
@@ -366,9 +373,16 @@ class Frozen(BaseCacheImpl[KT, VT]): # pragma: no cover
|
|
|
366
373
|
def _cast_lock(
|
|
367
374
|
iscoroutinefunction: bool,
|
|
368
375
|
lock: (
|
|
369
|
-
typing.Type[AbstractContextManager]
|
|
376
|
+
typing.Type[AbstractContextManager]
|
|
377
|
+
| typing.Type[AbstractAsyncContextManager]
|
|
378
|
+
| bool
|
|
379
|
+
| None
|
|
370
380
|
) = True,
|
|
371
|
-
) ->
|
|
381
|
+
) -> (
|
|
382
|
+
typing.Type[AbstractContextManager]
|
|
383
|
+
| typing.Type[AbstractAsyncContextManager]
|
|
384
|
+
| None
|
|
385
|
+
):
|
|
372
386
|
import _thread
|
|
373
387
|
import asyncio
|
|
374
388
|
import threading
|
|
@@ -381,12 +395,18 @@ def _cast_lock(
|
|
|
381
395
|
|
|
382
396
|
if iscoroutinefunction:
|
|
383
397
|
if not hasattr(lock, "__aenter__"):
|
|
384
|
-
raise TypeError(
|
|
398
|
+
raise TypeError(
|
|
399
|
+
"For async functions, you cannot use a regular synchronous lock."
|
|
400
|
+
)
|
|
385
401
|
|
|
386
402
|
return typing.cast(typing.Type[AbstractAsyncContextManager], lock)
|
|
387
403
|
|
|
388
404
|
# threading.Lock, threading.RLock and _thread.allocate_lock are function
|
|
389
|
-
if
|
|
405
|
+
if (
|
|
406
|
+
lock is threading.Lock
|
|
407
|
+
or lock is threading.RLock
|
|
408
|
+
or lock is _thread.allocate_lock
|
|
409
|
+
):
|
|
390
410
|
return typing.cast(typing.Type[AbstractContextManager], lock)
|
|
391
411
|
|
|
392
412
|
if not hasattr(lock, "__enter__"):
|
|
@@ -403,7 +423,10 @@ def cached(
|
|
|
403
423
|
copy_level: int = 1,
|
|
404
424
|
postprocess: _PostProcess | None = postprocess_copy_mutables,
|
|
405
425
|
lock: (
|
|
406
|
-
typing.Type[AbstractContextManager]
|
|
426
|
+
typing.Type[AbstractContextManager]
|
|
427
|
+
| typing.Type[AbstractAsyncContextManager]
|
|
428
|
+
| bool
|
|
429
|
+
| None
|
|
407
430
|
) = True,
|
|
408
431
|
) -> typing.Callable[[FT], FT]:
|
|
409
432
|
"""
|
|
@@ -480,7 +503,9 @@ def cached(
|
|
|
480
503
|
lock_type = _cast_lock(iscoroutinefunction, lock)
|
|
481
504
|
|
|
482
505
|
if not iscoroutinefunction and inspect.iscoroutinefunction(callback):
|
|
483
|
-
raise TypeError(
|
|
506
|
+
raise TypeError(
|
|
507
|
+
"For sync functions, you cannot use a asynchronous callback"
|
|
508
|
+
)
|
|
484
509
|
|
|
485
510
|
if lock_type:
|
|
486
511
|
builder = _async_cached_wrapper if iscoroutinefunction else _cached_wrapper
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
pub(crate) use self::inner::do_alloc;
|
|
2
|
+
#[cfg(test)]
|
|
3
|
+
pub(crate) use self::inner::AllocError;
|
|
4
|
+
pub(crate) use self::inner::Allocator;
|
|
5
|
+
pub(crate) use self::inner::Global;
|
|
6
|
+
|
|
7
|
+
mod inner {
|
|
8
|
+
#[cfg(test)]
|
|
9
|
+
pub(crate) use allocator_api2::alloc::AllocError;
|
|
10
|
+
pub(crate) use allocator_api2::alloc::Allocator;
|
|
11
|
+
pub(crate) use allocator_api2::alloc::Global;
|
|
12
|
+
use core::alloc::Layout;
|
|
13
|
+
use core::ptr::NonNull;
|
|
14
|
+
|
|
15
|
+
pub(crate) fn do_alloc<A: Allocator>(alloc: &A, layout: Layout) -> Result<NonNull<[u8]>, ()> {
|
|
16
|
+
match alloc.allocate(layout) {
|
|
17
|
+
Ok(ptr) => Ok(ptr),
|
|
18
|
+
Err(_) => Err(()),
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -4,7 +4,6 @@ use super::control::Tag;
|
|
|
4
4
|
use super::control::TagSliceExt;
|
|
5
5
|
use super::scopeguard::guard;
|
|
6
6
|
use super::scopeguard::ScopeGuard;
|
|
7
|
-
use super::util::invalid_mut;
|
|
8
7
|
use super::util::likely;
|
|
9
8
|
use super::util::unlikely;
|
|
10
9
|
use super::TryReserveError;
|
|
@@ -333,7 +332,7 @@ impl<T> Bucket<T> {
|
|
|
333
332
|
// won't overflow because index must be less than length (bucket_mask)
|
|
334
333
|
// and bucket_mask is guaranteed to be less than `isize::MAX`
|
|
335
334
|
// (see TableLayout::calculate_layout_for method)
|
|
336
|
-
|
|
335
|
+
ptr::without_provenance_mut(index + 1)
|
|
337
336
|
} else {
|
|
338
337
|
unsafe { base.as_ptr().sub(index) }
|
|
339
338
|
};
|
|
@@ -422,7 +421,7 @@ impl<T> Bucket<T> {
|
|
|
422
421
|
if T::IS_ZERO_SIZED {
|
|
423
422
|
// Just return an arbitrary ZST pointer which is properly aligned
|
|
424
423
|
// invalid pointer is good enough for ZST
|
|
425
|
-
|
|
424
|
+
ptr::without_provenance_mut(mem::align_of::<T>())
|
|
426
425
|
} else {
|
|
427
426
|
unsafe { self.ptr.as_ptr().sub(1) }
|
|
428
427
|
}
|
|
@@ -470,7 +469,7 @@ impl<T> Bucket<T> {
|
|
|
470
469
|
unsafe fn next_n(&self, offset: usize) -> Self {
|
|
471
470
|
let ptr = if T::IS_ZERO_SIZED {
|
|
472
471
|
// invalid pointer is good enough for ZST
|
|
473
|
-
|
|
472
|
+
ptr::without_provenance_mut(self.ptr.as_ptr() as usize + offset)
|
|
474
473
|
} else {
|
|
475
474
|
unsafe { self.ptr.as_ptr().sub(offset) }
|
|
476
475
|
};
|
|
@@ -3479,7 +3478,7 @@ impl<T, A: Allocator + Default> Default for RawTable<T, A> {
|
|
|
3479
3478
|
}
|
|
3480
3479
|
}
|
|
3481
3480
|
|
|
3482
|
-
|
|
3481
|
+
impl<T, A: Allocator> Drop for RawTable<T, A> {
|
|
3483
3482
|
#[cfg_attr(feature = "inline-more", inline)]
|
|
3484
3483
|
fn drop(&mut self) {
|
|
3485
3484
|
// SAFETY:
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#[inline(always)]
|
|
2
|
+
#[cold]
|
|
3
|
+
fn cold_path() {}
|
|
4
|
+
|
|
5
|
+
#[inline(always)]
|
|
6
|
+
pub(crate) fn likely(b: bool) -> bool {
|
|
7
|
+
if b {
|
|
8
|
+
true
|
|
9
|
+
} else {
|
|
10
|
+
cold_path();
|
|
11
|
+
false
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
#[inline(always)]
|
|
16
|
+
pub(crate) fn unlikely(b: bool) -> bool {
|
|
17
|
+
if b {
|
|
18
|
+
cold_path();
|
|
19
|
+
true
|
|
20
|
+
} else {
|
|
21
|
+
false
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -103,7 +103,7 @@ impl<T> OnceInit<T> {
|
|
|
103
103
|
/// Panics if called before [`set`](Self::set) has completed.
|
|
104
104
|
#[inline]
|
|
105
105
|
pub fn get(&self) -> &T {
|
|
106
|
-
if
|
|
106
|
+
if crate::hashbrown::util::likely(self.0.state.load(atomic::Ordering::Acquire) == INIT) {
|
|
107
107
|
// SAFETY: state == INIT guarantees `value` was fully written and is valid.
|
|
108
108
|
unsafe { (*self.0.value.get()).assume_init_ref() }
|
|
109
109
|
} else {
|
|
@@ -15,7 +15,7 @@ pub unsafe fn pyobject_hash(
|
|
|
15
15
|
arg1: *mut pyo3::ffi::PyObject,
|
|
16
16
|
) -> pyo3::PyResult<u64> {
|
|
17
17
|
let py_hash = pyo3::ffi::PyObject_Hash(arg1);
|
|
18
|
-
if
|
|
18
|
+
if crate::hashbrown::util::unlikely(py_hash == -1) {
|
|
19
19
|
// SAFETY: PyObject_Hash never returns -1 on success.
|
|
20
20
|
return Err(pyo3::PyErr::take(py).unwrap());
|
|
21
21
|
}
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
#![feature(allocator_api)]
|
|
2
|
-
#![feature(dropck_eyepatch)]
|
|
3
|
-
#![feature(likely_unlikely)]
|
|
4
|
-
|
|
5
1
|
#[macro_use]
|
|
6
2
|
mod macro_rules;
|
|
7
3
|
mod hashbrown;
|
|
@@ -71,22 +67,22 @@ mod _core {
|
|
|
71
67
|
#[pymodule_export]
|
|
72
68
|
use crate::pyclasses::ttlcache::PyTTLCacheItems;
|
|
73
69
|
#[pymodule_export]
|
|
70
|
+
use crate::pyclasses::ttlcache::PyTTLCacheItemsWithExpire;
|
|
71
|
+
#[pymodule_export]
|
|
74
72
|
use crate::pyclasses::ttlcache::PyTTLCacheKeys;
|
|
75
73
|
#[pymodule_export]
|
|
76
74
|
use crate::pyclasses::ttlcache::PyTTLCacheValues;
|
|
77
|
-
#[pymodule_export]
|
|
78
|
-
use crate::pyclasses::ttlcache::PyTTLCacheItemsWithExpire;
|
|
79
75
|
|
|
80
76
|
#[pymodule_export]
|
|
81
77
|
use crate::pyclasses::vttlcache::PyVTTLCache;
|
|
82
78
|
#[pymodule_export]
|
|
83
79
|
use crate::pyclasses::vttlcache::PyVTTLCacheItems;
|
|
84
80
|
#[pymodule_export]
|
|
81
|
+
use crate::pyclasses::vttlcache::PyVTTLCacheItemsWithExpire;
|
|
82
|
+
#[pymodule_export]
|
|
85
83
|
use crate::pyclasses::vttlcache::PyVTTLCacheKeys;
|
|
86
84
|
#[pymodule_export]
|
|
87
85
|
use crate::pyclasses::vttlcache::PyVTTLCacheValues;
|
|
88
|
-
#[pymodule_export]
|
|
89
|
-
use crate::pyclasses::vttlcache::PyVTTLCacheItemsWithExpire;
|
|
90
86
|
|
|
91
87
|
#[pymodule_init]
|
|
92
88
|
pub fn init(m: &pyo3::Bound<'_, pyo3::types::PyModule>) -> pyo3::PyResult<()> {
|
|
@@ -299,7 +299,8 @@ impl PolicyExt for FIFOPolicy {
|
|
|
299
299
|
let front = unsafe { front.unwrap_unchecked() };
|
|
300
300
|
|
|
301
301
|
let eq = |index: &usize| Ok::<_, pyo3::PyErr>(*index - self.front_offset == 0);
|
|
302
|
-
|
|
302
|
+
let removed_key_table = self.table.remove_entry(front.key().hash(), eq).unwrap();
|
|
303
|
+
if crate::hashbrown::util::unlikely(removed_key_table.is_none()) {
|
|
303
304
|
unreachable!("popitem key not found in table");
|
|
304
305
|
}
|
|
305
306
|
|
|
@@ -322,12 +322,8 @@ impl TTLPolicy {
|
|
|
322
322
|
}
|
|
323
323
|
|
|
324
324
|
let eq = |index: &usize| Ok::<_, pyo3::PyErr>((*index - self.front_offset) == 0);
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
.remove_entry(handle.key().hash(), eq)
|
|
328
|
-
.unwrap()
|
|
329
|
-
.is_none(),
|
|
330
|
-
) {
|
|
325
|
+
let removed_key_table = self.table.remove_entry(handle.key().hash(), eq).unwrap();
|
|
326
|
+
if crate::hashbrown::util::unlikely(removed_key_table.is_none()) {
|
|
331
327
|
unreachable!("popitem key not found in table");
|
|
332
328
|
}
|
|
333
329
|
|
|
@@ -430,7 +426,8 @@ impl PolicyExt for TTLPolicy {
|
|
|
430
426
|
let front = unsafe { front.unwrap_unchecked() };
|
|
431
427
|
|
|
432
428
|
let eq = |index: &usize| Ok::<_, pyo3::PyErr>(*index - self.front_offset == 0);
|
|
433
|
-
|
|
429
|
+
let removed_key_table = self.table.remove_entry(front.key().hash(), eq).unwrap();
|
|
430
|
+
if crate::hashbrown::util::unlikely(removed_key_table.is_none()) {
|
|
434
431
|
unreachable!("popitem key not found in table");
|
|
435
432
|
}
|
|
436
433
|
|
|
@@ -632,7 +632,7 @@ async def test_async_recursive_cached_issue_54(
|
|
|
632
632
|
def test_recursive_cached(random_cache_impl: type[cachebox.BaseCacheImpl]):
|
|
633
633
|
obj = random_cache_impl(10)
|
|
634
634
|
|
|
635
|
-
@cachebox.cached(obj)
|
|
635
|
+
@cachebox.cached(obj, lock=False)
|
|
636
636
|
def factorial(n):
|
|
637
637
|
if n < 0:
|
|
638
638
|
raise ValueError
|
|
@@ -654,7 +654,7 @@ def test_recursive_threading_cached():
|
|
|
654
654
|
|
|
655
655
|
obj = cachebox.LRUCache(10)
|
|
656
656
|
|
|
657
|
-
@cachebox.cached(obj)
|
|
657
|
+
@cachebox.cached(obj, lock=False)
|
|
658
658
|
def factorial(n):
|
|
659
659
|
if n < 0:
|
|
660
660
|
raise ValueError
|
|
@@ -680,7 +680,7 @@ def test_recursive_threading_cached():
|
|
|
680
680
|
async def test_recursive_asyncio_cached():
|
|
681
681
|
obj = cachebox.LRUCache(10)
|
|
682
682
|
|
|
683
|
-
@cachebox.cached(obj)
|
|
683
|
+
@cachebox.cached(obj, lock=False)
|
|
684
684
|
async def factorial(n) -> int:
|
|
685
685
|
if n < 0:
|
|
686
686
|
raise ValueError
|
|
@@ -742,3 +742,124 @@ async def test_async_handling_pending_errors():
|
|
|
742
742
|
await calc(1, 2, 3, exception=True)
|
|
743
743
|
|
|
744
744
|
assert await calc(1, 2, 3, exception=False) == 6
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
def test_misses_should_increment_without_lock():
|
|
748
|
+
calls = {"n": 0}
|
|
749
|
+
|
|
750
|
+
@cachebox.cached(cachebox.LRUCache(0), lock=False)
|
|
751
|
+
def f(x):
|
|
752
|
+
calls["n"] += 1
|
|
753
|
+
return x * 2
|
|
754
|
+
|
|
755
|
+
assert f(1) == 2 # miss
|
|
756
|
+
assert f(1) == 2 # hit
|
|
757
|
+
assert f(2) == 4 # miss
|
|
758
|
+
|
|
759
|
+
info = cachebox.get_cached_cache_info(f)
|
|
760
|
+
print(info)
|
|
761
|
+
|
|
762
|
+
assert info.misses == 2
|
|
763
|
+
assert info.hits == 1
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
@pytest.mark.asyncio
|
|
767
|
+
async def test_ignore_path_should_return_awaited_value():
|
|
768
|
+
@cachebox.cached(cachebox.LRUCache(0), lock=False)
|
|
769
|
+
async def f(x):
|
|
770
|
+
await asyncio.sleep(0)
|
|
771
|
+
return x * 2
|
|
772
|
+
|
|
773
|
+
result = await f(
|
|
774
|
+
5,
|
|
775
|
+
cachebox__ignore=True, # type: ignore
|
|
776
|
+
)
|
|
777
|
+
assert not asyncio.iscoroutine(result)
|
|
778
|
+
assert result == 10
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
def test_locks_should_not_leak_on_exception():
|
|
782
|
+
@cachebox.cached(cachebox.LRUCache(0)) # lock پیشفرض True است
|
|
783
|
+
def f(x):
|
|
784
|
+
raise ValueError("boom")
|
|
785
|
+
|
|
786
|
+
for i in range(50):
|
|
787
|
+
with pytest.raises(ValueError):
|
|
788
|
+
f(i)
|
|
789
|
+
|
|
790
|
+
locks_cache = None
|
|
791
|
+
for cell in f.__closure__: # type: ignore
|
|
792
|
+
try:
|
|
793
|
+
val = cell.cell_contents
|
|
794
|
+
except ValueError:
|
|
795
|
+
continue
|
|
796
|
+
|
|
797
|
+
if isinstance(val, cachebox.Cache):
|
|
798
|
+
locks_cache = val
|
|
799
|
+
break
|
|
800
|
+
|
|
801
|
+
assert locks_cache is not None, "We couldn't find locks_cache"
|
|
802
|
+
print("locks length after 50 failing calls:", len(locks_cache))
|
|
803
|
+
|
|
804
|
+
assert len(locks_cache) == 0
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
@pytest.mark.asyncio
|
|
808
|
+
async def test_waiters_should_not_leak_on_cancel():
|
|
809
|
+
started = asyncio.Event()
|
|
810
|
+
release = asyncio.Event()
|
|
811
|
+
|
|
812
|
+
@cachebox.cached(cachebox.LRUCache(0))
|
|
813
|
+
async def f(x):
|
|
814
|
+
started.set()
|
|
815
|
+
await release.wait()
|
|
816
|
+
return x
|
|
817
|
+
|
|
818
|
+
first = asyncio.create_task(f(1))
|
|
819
|
+
await started.wait()
|
|
820
|
+
|
|
821
|
+
second = asyncio.create_task(f(1))
|
|
822
|
+
await asyncio.sleep(0.05)
|
|
823
|
+
|
|
824
|
+
second.cancel()
|
|
825
|
+
with pytest.raises(asyncio.CancelledError):
|
|
826
|
+
await second
|
|
827
|
+
|
|
828
|
+
release.set()
|
|
829
|
+
result = await first
|
|
830
|
+
assert result == 1
|
|
831
|
+
|
|
832
|
+
locks_cache = None
|
|
833
|
+
for cell in f.__closure__: # type: ignore
|
|
834
|
+
try:
|
|
835
|
+
val = cell.cell_contents
|
|
836
|
+
except ValueError:
|
|
837
|
+
continue
|
|
838
|
+
if isinstance(val, cachebox.Cache):
|
|
839
|
+
locks_cache = val
|
|
840
|
+
break
|
|
841
|
+
assert locks_cache is not None
|
|
842
|
+
|
|
843
|
+
print("locks length after cancel scenario:", len(locks_cache))
|
|
844
|
+
assert len(locks_cache) == 0
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
@pytest.mark.asyncio
|
|
848
|
+
async def test_cancelled_error_should_propagate_to_waiters_too():
|
|
849
|
+
gate = asyncio.Event()
|
|
850
|
+
|
|
851
|
+
@cachebox.cached(cachebox.LRUCache(0))
|
|
852
|
+
async def f(x):
|
|
853
|
+
await gate.wait()
|
|
854
|
+
raise asyncio.CancelledError()
|
|
855
|
+
|
|
856
|
+
t1 = asyncio.create_task(f(1))
|
|
857
|
+
t2 = asyncio.create_task(f(1))
|
|
858
|
+
await asyncio.sleep(0.01)
|
|
859
|
+
gate.set()
|
|
860
|
+
|
|
861
|
+
with pytest.raises(asyncio.CancelledError):
|
|
862
|
+
await t1
|
|
863
|
+
|
|
864
|
+
with pytest.raises(asyncio.CancelledError):
|
|
865
|
+
await t2
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
use core::ptr::NonNull;
|
|
2
|
-
|
|
3
|
-
#[cfg(test)]
|
|
4
|
-
pub(crate) use std::alloc::AllocError;
|
|
5
|
-
use std::alloc::Layout;
|
|
6
|
-
pub(crate) use std::alloc::{Allocator, Global};
|
|
7
|
-
|
|
8
|
-
pub(crate) fn do_alloc<A: Allocator>(alloc: &A, layout: Layout) -> Result<NonNull<[u8]>, ()> {
|
|
9
|
-
match alloc.allocate(layout) {
|
|
10
|
-
Ok(ptr) => Ok(ptr),
|
|
11
|
-
Err(_) => Err(()),
|
|
12
|
-
}
|
|
13
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
pub(crate) use std::hint::likely;
|
|
2
|
-
pub(crate) use std::hint::unlikely;
|
|
3
|
-
|
|
4
|
-
// FIXME: use strict provenance functions once they are stable.
|
|
5
|
-
// Implement it with a transmute for now.
|
|
6
|
-
#[inline(always)]
|
|
7
|
-
pub(crate) fn invalid_mut<T>(addr: usize) -> *mut T {
|
|
8
|
-
unsafe { core::mem::transmute(addr) }
|
|
9
|
-
}
|
|
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
|
|
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
|