Skip to content

perf(layout): don't pin id-keyed source images in fit_image's cache - #732

Merged
ChuckBuilds merged 1 commit into
mainfrom
perf/layout-image-cache-weak-source
Oct 3, 2026
Merged

ChuckBuilds merged 1 commit into
mainfrom
perf/layout-image-cache-weak-source

Conversation

@ChuckBuilds

@ChuckBuilds ChuckBuilds commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

Summary

LayoutContext.fit_image caches fitted images in a 64-entry LRU. With no cache_key, the key is id(img), and the entry used to hold a strong reference to the source so the id couldn't be recycled. A caller that passes a freshly loaded image every frame, which is the documented draw_image(Image.open(path), box) one-liner, never hits that cache and keeps the last 64 sources alive. For 500x500 RGBA logos (the median under assets/sports) that's ~64 MB, and up to ~600 MB with the largest logo in assets.

An id-keyed entry now holds a weak reference to its source, and a callback drops the entry when the source is freed. A hit re-checks that the referent is the same image. Sources that can't be weak-referenced are still pinned, and keyed entries are unchanged.

Type of change

  • Bug fix

Test plan

  • Ran the test suite (pytest): the old "pins source" test is replaced by 4 tests (entry doesn't pin and dies with its source, fresh-per-frame holds 0 entries, a recycled id doesn't alias, an unweakrefable source is still pinned), all of which fail against the old code. The full suite passes apart from the psutil-dependent test_live_status_fields tests, which fail the same way on main.
  • Ran on a real Raspberry Pi with hardware: not needed. Nothing calls this path unkeyed today.

Plugin compatibility

  • No plugin breakage expected. On ledmatrix-plugins main the only fit_image caller (football-scoreboard game_renderer.py) passes a cache_key, and no plugin calls BasePlugin.draw_image. This is hardening for the documented usage, not a fix for a live leak.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Image fitting no longer keeps otherwise-unused source images alive solely because they were cached. Cache entries are cleared when their source images are released, helping prevent unnecessary memory retention when fitting freshly loaded images.
    • Stale cache entries are detected and refitted instead of returning outdated results. Images that cannot be weakly referenced continue to be retained so their cache entries remain usable.

LayoutContext.fit_image keyed images without a cache_key by id() and held
a strong reference to the source so the id could not be recycled. A
plugin following the documented one-liner -- draw_image(Image.open(path),
box) each frame -- never hit that cache and kept the last 64 sources
alive: ~64MB for 500x500 RGBA team logos (median size under
assets/sports), up to ~600MB for the largest.

The entry now holds a weak reference whose callback drops it when the
source is freed, and a hit re-checks that the referent is the same
image. Sources that cannot be weak-referenced are still pinned. Keyed
entries (the only kind any plugin on ledmatrix-plugins main uses today:
football-scoreboard's logo fit) are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 8c1cf2b2-93e4-4348-b17a-3e8590b652e8

📥 Commits

Reviewing files that changed from the base of the PR and between 8136a2d and a8f1148.

📒 Files selected for processing (2)
  • src/adaptive_layout.py
  • test/test_adaptive_images.py

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

fit_image now weakly tracks id-keyed source images, validates cache hits against those images, and removes entries when their sources are collected. It retains sources strongly when they cannot be weak-referenced. Tests cover these behaviors and explicit cache reuse.

Changes

Image cache source tracking

Layer / File(s) Summary
Cache identity and source lifetime
src/adaptive_layout.py, test/test_adaptive_images.py
fit_image validates id-keyed cache hits against their source and removes entries when weakly referenced sources are collected. It falls back to a strong reference when weak references are unsupported. Tests cover collection, stale hits, temporary images, and fallback behavior.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to a8f11

No actionable merge-blocking risk remains; the image-cache change is mergeable after normal checks.

Security Architecture Review

Security architecture risk: ⚪ Minimal · up to a8f11

The change reduces source-image retention without expanding drawing authority or changing the public API. Identity checks and guarded cleanup preserve the inspected cache lifecycle. No material security risk was found in the affected drawing path.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated exposure is the image cache attached to a plugin's LayoutContext and its existing drawing consumer. No new cross-service, tenant, credential, or persistent-state access is introduced by the inspected path; deployment-wide exposure was not established.

Trust Boundaries and Controls

  • observed — The added referent check controls cache correctness, not authorization. Explicit cache keys remain caller-controlled identity assertions and bypass that check as before; the PR does not introduce a new security boundary around image contents.

Resilience and Maintainability Implications

  • observed — The implementation supports the inspected sequential lifecycle but has no lock or transaction spanning lookup, fitting, insertion, cleanup, and eviction. Available tests do not establish concurrent or interruption-atomic behavior. This remains an evidence limitation rather than a demonstrated PR-introduced security concern.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: preventing id-keyed source images from being retained strongly in fit_image's cache.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 complexity

Metric Results
Complexity 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@ChuckBuilds
ChuckBuilds merged commit 6bf7c3f into main Oct 3, 2026
13 checks passed
@ChuckBuilds
ChuckBuilds deleted the perf/layout-image-cache-weak-source branch October 3, 2026 16:11
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.

1 participant