mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 06:15:09 +00:00
perf(cache): skip rewriting unchanged CacheManager.set() records (#730)
The disk cache's unchanged-payload skip now ignores a CacheManager.set() record's timestamp, so unchanged re-saves are skipped; a skip moves the file's mtime to the new timestamp instead, and readers take a record's age from the newer of the two (never more than an hour past the embedded timestamp). Per-plugin plugin_metrics:<id> records become one plugin_metrics_snapshot written at most once a minute, and CacheManager builds its ConfigManager on first use. On hdpi, cache file writes went from ~37 to 8.6 a minute. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
"""CacheManager builds its ConfigManager on first use, not in __init__.
|
||||
|
||||
Every CacheManager built a ConfigManager and loaded the whole config for a
|
||||
cache strategy that no longer reads it. The attribute stays public -- the
|
||||
sports plugins resolve the global timezone through
|
||||
``cache_manager.config_manager`` -- so it is now built on first access.
|
||||
"""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
import src.config_manager as config_manager_module
|
||||
from src.cache_manager import CacheManager
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def built(monkeypatch):
|
||||
"""Count ConfigManager constructions and load_config calls."""
|
||||
made = []
|
||||
|
||||
class CountingConfigManager:
|
||||
def __init__(self):
|
||||
made.append(self)
|
||||
self.loads = 0
|
||||
|
||||
def load_config(self):
|
||||
self.loads += 1
|
||||
return {}
|
||||
|
||||
monkeypatch.setattr(config_manager_module, "ConfigManager", CountingConfigManager)
|
||||
return made
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def manager(tmp_path):
|
||||
with patch('src.cache_manager.CacheManager._get_writable_cache_dir',
|
||||
return_value=str(tmp_path)):
|
||||
cm = CacheManager()
|
||||
cm.stop_cleanup_thread()
|
||||
return cm
|
||||
|
||||
|
||||
def test_construction_does_not_load_the_config(built, manager):
|
||||
manager.set("k", {"v": 1})
|
||||
assert manager.get("k") == {"v": 1}
|
||||
assert manager.get_cache_strategy("sports_live")["max_age"] > 0
|
||||
assert built == []
|
||||
|
||||
|
||||
def test_first_access_builds_and_loads_it_once(built, manager):
|
||||
first = manager.config_manager
|
||||
assert manager.config_manager is first
|
||||
assert getattr(manager, "config_manager", None) is first
|
||||
assert len(built) == 1 and first.loads == 1
|
||||
|
||||
|
||||
def test_assignment_still_wins(built, manager):
|
||||
replacement = MagicMock()
|
||||
manager.config_manager = replacement
|
||||
assert manager.config_manager is replacement
|
||||
assert built == []
|
||||
|
||||
|
||||
def test_an_unimportable_config_manager_is_none(manager, monkeypatch):
|
||||
import builtins
|
||||
real_import = builtins.__import__
|
||||
|
||||
def refuse(name, *args, **kwargs):
|
||||
if name == "src.config_manager":
|
||||
raise ImportError("no config manager here")
|
||||
return real_import(name, *args, **kwargs)
|
||||
|
||||
monkeypatch.setattr(builtins, "__import__", refuse)
|
||||
assert manager.config_manager is None
|
||||
|
||||
|
||||
def test_a_failed_load_is_retried_on_the_next_access(manager, monkeypatch):
|
||||
attempts = []
|
||||
|
||||
class Flaky:
|
||||
def load_config(self):
|
||||
attempts.append(1)
|
||||
if len(attempts) == 1:
|
||||
raise RuntimeError("config.json unreadable")
|
||||
return {}
|
||||
|
||||
monkeypatch.setattr(config_manager_module, "ConfigManager", Flaky)
|
||||
with pytest.raises(RuntimeError):
|
||||
manager.config_manager
|
||||
assert isinstance(manager.config_manager, Flaky)
|
||||
assert len(attempts) == 2
|
||||
|
||||
|
||||
def test_a_manager_made_without_init_still_answers(built):
|
||||
bare = CacheManager.__new__(CacheManager)
|
||||
bare.logger = MagicMock()
|
||||
assert bare.config_manager is built[0]
|
||||
Reference in New Issue
Block a user