From 75eed14a1a3fe94492b76f0f11b677854f17cd0a Mon Sep 17 00:00:00 2001 From: allen0099 Date: Sat, 26 Sep 2026 12:22:37 +0000 Subject: [PATCH] fix(memory): reject a cleanup_interval that is not positive With 0 or a negative value asyncio.sleep() returned at once, so the cleanup loop spun on a full CPU core and took the cache lock on every pass. MemoryBackend now raises ValueError for it. Closes #180 --- CHANGELOG.md | 6 ++++++ docs/BACKENDS.md | 3 ++- fastapi_cachex/backends/memory.py | 8 ++++++++ i18n/zh-TW/docs/BACKENDS.md | 2 +- tests/backends/test_memory.py | 7 +++++++ 5 files changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a4c8cb..475a41f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,12 @@ Note that 0.3.3 was never released; 0.3.4 follows 0.3.2. `delete()` and the other methods let the error through. ([#177](https://github.com/allen0099/FastAPI-CacheX/issues/177)) +- **`MemoryBackend` rejects a `cleanup_interval` that is not positive.** With + `0` or a negative value, `asyncio.sleep()` returned at once and the cleanup + loop spun, using a full CPU core and taking the cache lock on every pass. It + now raises `ValueError`. + ([#180](https://github.com/allen0099/FastAPI-CacheX/issues/180)) + ## [0.3.7] - 2026-09-25 ### Added diff --git a/docs/BACKENDS.md b/docs/BACKENDS.md index 02d3f4d..9d096df 100644 --- a/docs/BACKENDS.md +++ b/docs/BACKENDS.md @@ -19,7 +19,8 @@ All backends namespace their keys with a prefix (`fastapi_cachex:` by default, If you don't specify a backend, FastAPI-CacheX will use the in-memory cache by default. This is suitable for development and testing purposes. The backend automatically runs -a cleanup task to remove expired entries every 60 seconds (`MemoryBackend(cleanup_interval=60)`). +a cleanup task to remove expired entries every 60 seconds (`MemoryBackend(cleanup_interval=60)`; +the interval must be positive). ```python from fastapi_cachex.backends import MemoryBackend diff --git a/fastapi_cachex/backends/memory.py b/fastapi_cachex/backends/memory.py index 2b31c1c..bf44bb4 100644 --- a/fastapi_cachex/backends/memory.py +++ b/fastapi_cachex/backends/memory.py @@ -56,7 +56,15 @@ def __init__(self, cleanup_interval: int = 60) -> None: Args: cleanup_interval: Interval in seconds between cleanup runs (default: 60) + + Raises: + ValueError: If ``cleanup_interval`` is not positive """ + if cleanup_interval <= 0: + # asyncio.sleep() returns at once for these, so the cleanup loop + # would spin, taking the cache lock on every pass. + msg = f"cleanup_interval must be positive, got {cleanup_interval!r}" + raise ValueError(msg) self.cache: dict[str, CacheItem] = {} self.lock = asyncio.Lock() self.cleanup_interval = cleanup_interval diff --git a/i18n/zh-TW/docs/BACKENDS.md b/i18n/zh-TW/docs/BACKENDS.md index 3ef8931..d6fc9ac 100644 --- a/i18n/zh-TW/docs/BACKENDS.md +++ b/i18n/zh-TW/docs/BACKENDS.md @@ -15,7 +15,7 @@ ## 記憶體(預設) {#in-memory-default} -若未指定後端,FastAPI-CacheX 預設會使用記憶體快取。這適合開發與測試用途。此後端會自動執行清理工作,每 60 秒移除一次已過期的項目(`MemoryBackend(cleanup_interval=60)`)。 +若未指定後端,FastAPI-CacheX 預設會使用記憶體快取。這適合開發與測試用途。此後端會自動執行清理工作,每 60 秒移除一次已過期的項目(`MemoryBackend(cleanup_interval=60)`;間隔必須大於 0)。 ```python from fastapi_cachex.backends import MemoryBackend diff --git a/tests/backends/test_memory.py b/tests/backends/test_memory.py index 40009fa..3d6b403 100644 --- a/tests/backends/test_memory.py +++ b/tests/backends/test_memory.py @@ -16,6 +16,13 @@ def memory_backend(): return MemoryBackend() +@pytest.mark.parametrize("interval", [0, -5]) +def test_cleanup_interval_must_be_positive(interval: int) -> None: + """A non-positive interval made the cleanup loop spin (#180).""" + with pytest.raises(ValueError, match="cleanup_interval must be positive"): + MemoryBackend(cleanup_interval=interval) + + @pytest.mark.asyncio async def test_memory_backend_set_get(memory_backend: MemoryBackend): key = "test_key"