mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 06:15:09 +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:
@@ -47,3 +47,28 @@ def test_debug_output_appears_when_the_root_is_at_debug():
|
||||
assert dm.logger.isEnabledFor(logging.DEBUG)
|
||||
finally:
|
||||
root.setLevel(previous)
|
||||
|
||||
|
||||
def test_update_display_errors_are_rate_limited(caplog):
|
||||
# update_display() runs every frame; a persistent fault logged an ERROR
|
||||
# line per frame (~100 a second) and never a traceback.
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
dm_obj = object.__new__(dm.DisplayManager)
|
||||
dm_obj._writes_suppressed = MagicMock(side_effect=RuntimeError("boom"))
|
||||
|
||||
with caplog.at_level(logging.ERROR, logger='src.display_manager'):
|
||||
for _ in range(50):
|
||||
dm_obj.update_display() # must not raise
|
||||
errors = [r for r in caplog.records
|
||||
if r.getMessage().startswith('Error updating display')]
|
||||
assert len(errors) == 1
|
||||
assert errors[0].exc_info is not None
|
||||
|
||||
# Once the interval has passed, one more line reports what was skipped.
|
||||
dm_obj._update_error_logged_at -= dm._UPDATE_ERROR_LOG_INTERVAL + 1
|
||||
dm_obj.update_display()
|
||||
errors = [r for r in caplog.records
|
||||
if r.getMessage().startswith('Error updating display')]
|
||||
assert len(errors) == 2
|
||||
assert '49 more' in errors[1].getMessage()
|
||||
|
||||
Reference in New Issue
Block a user