Skip to content

perf: move image generation off the HA event loop (fixes #17)#27

Draft
Misiu with Copilot wants to merge 3 commits into
mainfrom
copilot/investigate-image-generation-issue
Draft

perf: move image generation off the HA event loop (fixes #17)#27
Misiu with Copilot wants to merge 3 commits into
mainfrom
copilot/investigate-image-generation-issue

Conversation

Copilot AI commented Apr 28, 2026

Copy link
Copy Markdown

Summary

Fixes the HA event-loop blocking described in OpenDisplay/Home_Assistant_Integration#17.

All PIL drawing operations (text, shapes, QR codes, image downloads/transforms, icon rendering, JPEG encoding) previously ran synchronously on the HA event loop, causing HA to lock up for tens of seconds per image update.

Root causes fixed

Problem Fix
All PIL CPU work blocked the event loop Entire drawing pipeline moved to hass.async_add_executor_job
MDI icon JSON (~7 MB, 7 334 icons) re-read from disk on every icon element Module-level _mdi_metadata_cache + _mdi_font_cache — loaded once, reused forever
QR-code generation (qr.make()) ran on event loop Now in executor thread
Image resize/rotate/composite ran on event loop Now in executor thread
requests.get() was wrapped in extra async_add_executor_job inside an already-sync handler Simplified to a direct call (already in thread)

How it works now

Phase 1 – async (event loop)
_async_prefetch_resources() fetches only data that requires the HA event loop:

  • Recorder history for plot elements (via prefetch_plot_data)

Phase 2 – sync (executor thread)
_generate_sync() runs in hass.async_add_executor_job and handles:

  • PIL image creation, all drawing handlers, rotation, RGB conversion, JPEG encoding
  • Blocking HTTP/file I/O in draw_downloaded_image (safe in thread)
  • MDI font + metadata lookup from module-level cache

Changes

  • core.py: Two-phase generate_custom_image; new _async_prefetch_resources + _generate_sync methods
  • registry.py: Handler wrapper changed from async def to def
  • text.py, shapes.py, debug.py: async defdef (no I/O)
  • icons.py: Module-level MDI cache helpers; async defdef
  • media.py: async defdef; direct blocking I/O (in thread)
  • visualizations.py: New async prefetch_plot_data; all handlers async defdef
  • types.py: DrawingContext.resources field added (with docstring)
  • tests/drawcustom/conftest.py: async_add_executor_job mock now executes callables so existing 82 tests still pass

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants