mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 14:25:08 +00:00
fix(display): thread-safety for deferred updates, BDF faces and follower image; one refresh default (#652)
- DisplayManager.defer_update()/process_deferred_updates(): one lock around every queue mutation (appends from the update thread were lost to the render thread's filter/slice reassignments); callables run outside it. - FontManager and element_style no longer cache BDF freetype.Face objects process-wide (load_bdf_face caches them per thread); element_style's LRU is locked against get/move_to_end vs eviction races. - limit_refresh_rate_hz default is one constant, DEFAULT_REFRESH_LIMIT_HZ = 100 (the template's), for the library options, refresh_hz, the matrix guard, Vegas and scroll_config. Previously a missing key capped the panel at 90 while pacing assumed 100. - Sync follower: the TCP thread queues the leader's scroll image; the render thread swaps image/array/width in between frames. - update_display() error log rate-limited (traceback first, then once a minute with a count); swallowed DisplayController exceptions log at DEBUG. - Root display_controller.py runs run.py via runpy. - stream_manager: correct the RLock release comments; merge duplicate if. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
"""Errors DisplayController deliberately swallows must still leave a trace.
|
||||
|
||||
Several ``except Exception: pass`` blocks hid plugin and filesystem faults
|
||||
completely; they now log at DEBUG with the traceback, and still don't raise.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
os.environ.setdefault("EMULATOR", "true")
|
||||
|
||||
from src.display_controller import DisplayController # noqa: E402
|
||||
|
||||
|
||||
def test_has_live_content_failure_is_logged_not_raised(caplog):
|
||||
dc = object.__new__(DisplayController)
|
||||
broken = MagicMock()
|
||||
broken.has_live_content.side_effect = RuntimeError("feed parse failed")
|
||||
dc.plugin_display_modes = {"nfl": ["nfl_live", "nfl_recent"]}
|
||||
dc.mode_to_plugin_id = {}
|
||||
dc.plugin_modes = {"nfl_live": broken, "nfl_recent": MagicMock()}
|
||||
|
||||
with caplog.at_level(logging.DEBUG, logger="src.display_controller"):
|
||||
modes = dc._on_demand_modes_for_plugin("nfl")
|
||||
|
||||
# Behaviour unchanged: the raising live mode counts as having no content.
|
||||
assert modes == ["nfl_recent"]
|
||||
records = [r for r in caplog.records
|
||||
if "has_live_content() failed for nfl_live" in r.getMessage()]
|
||||
assert len(records) == 1 and records[0].exc_info is not None
|
||||
Reference in New Issue
Block a user