mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-12 14:18:06 +00:00
fix(cache): make the ttl parameter actually control expiry (#450)
CacheManager.set(key, data, ttl=...) stored the number and no read path
ever consulted it. Expiry came from a max_age inferred from substrings
in the key -- "live", "odds", "stock" -- so all 52 callers passing a ttl
were writing a value that did nothing. The docstring said so outright:
"stored for compatibility but expiration is still controlled via max_age
when reading". It is easier to read that as a note than as a defect,
which is presumably how it survived.
Both cache layers already hold the record when they decide, so each now
prefers an explicit ttl and falls back to max_age when there is none.
The caller that wrote the record knows what its data is; a substring
guess is a reasonable default for records that never said, and a poor
override for records that did.
Measured against a device's real cache of 8,875 entries carrying a ttl,
the inferred and intended values disagreed nearly everywhere:
stocks max_age 600 vs ttl 1800 4903 entries
news max_age 3600 vs ttl 600 1770 entries
odds max_age 1800 vs ttl 3600 1301 entries
images max_age 300 vs ttl 2592000 20 entries
In every case the ttl matches what the plugin plainly intended: stock
quotes cached for half an hour rather than ten minutes, headlines
refreshed every ten minutes rather than hourly, bird photographs that
never change kept for a month rather than five minutes.
Two things make this safe to land now. No sports_live entry carries a
ttl at all -- the live-score path does not use set(ttl=) -- so live
freshness is untouched, which matters with a season two weeks out. And
replaying the change against that real cache, 997 currently-expired
entries become live while not one live entry becomes expired, so there
is no invalidation spike on deploy.
Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vendored
+16
@@ -112,6 +112,22 @@ class DiskCache:
|
||||
record_ts = None
|
||||
|
||||
now = time.time()
|
||||
|
||||
# An explicit per-entry ttl wins over the caller's max_age. The
|
||||
# caller that wrote the record knows what its data is; max_age is
|
||||
# inferred from substrings in the key ("live", "odds", "stock") and
|
||||
# is only a fallback for records that never said. Until now the ttl
|
||||
# was stored and ignored, so `set(key, data, ttl=...)` did nothing
|
||||
# at all -- 48 plugin call sites and 4 in the core were writing a
|
||||
# number no read path consulted.
|
||||
effective_max_age = max_age
|
||||
if isinstance(record, dict):
|
||||
stored_ttl = record.get('ttl')
|
||||
if isinstance(stored_ttl, (int, float)) and not isinstance(stored_ttl, bool) \
|
||||
and stored_ttl >= 0:
|
||||
effective_max_age = stored_ttl
|
||||
max_age = effective_max_age
|
||||
|
||||
# max_age=None means "never expires" (mirrors MemoryCache and the
|
||||
# cache_manager docstring). Guard it explicitly — otherwise the
|
||||
# comparison below raises TypeError and the record is treated as a
|
||||
|
||||
Vendored
+10
@@ -57,6 +57,16 @@ class MemoryCache:
|
||||
if timestamp is None:
|
||||
return None
|
||||
|
||||
# An explicit per-entry ttl wins over the caller's max_age, matching
|
||||
# DiskCache. max_age is inferred from substrings in the key and is
|
||||
# only a fallback for records that did not say what they wanted.
|
||||
record = self._cache[key]
|
||||
if isinstance(record, dict):
|
||||
stored_ttl = record.get('ttl')
|
||||
if isinstance(stored_ttl, (int, float)) and not isinstance(stored_ttl, bool) \
|
||||
and stored_ttl >= 0:
|
||||
max_age = stored_ttl
|
||||
|
||||
# Check expiration
|
||||
if max_age is not None and (now - timestamp) > max_age:
|
||||
# Expired - remove it
|
||||
|
||||
@@ -594,8 +594,10 @@ class CacheManager:
|
||||
Args:
|
||||
key: Cache key
|
||||
data: Data to cache
|
||||
ttl: Optional time-to-live in seconds (stored for compatibility but
|
||||
expiration is still controlled via max_age when reading)
|
||||
ttl: Time-to-live in seconds for this entry. Takes precedence over
|
||||
the max_age a reader would otherwise apply, which is inferred
|
||||
from the key and is only a fallback for entries that did not
|
||||
say. Omit it to keep that inferred behaviour.
|
||||
"""
|
||||
cache_data = {
|
||||
'data': data,
|
||||
|
||||
Reference in New Issue
Block a user