Skip to content

fix(images): keep cached images independent of node-held objects - #10138

Merged
joshistoast merged 3 commits into
invoke-ai:mainfrom
Pfannkuchensack:fix/get-pil-conversion-isolation
Nov 3, 2026
Merged

joshistoast merged 3 commits into
invoke-ai:mainfrom
Pfannkuchensack:fix/get-pil-conversion-isolation

Conversation

@Pfannkuchensack

@Pfannkuchensack Pfannkuchensack commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Summary

The image file cache could hand out the same Pillow object that a node holds and keeps mutating:

  • Read side: context.images.get_pil(name, mode) returned the service's cached object itself when image.convert(mode) raised ValueError (e.g. a LAB image requested as L). A node that then drew on its result changed what every later reader of that image saw.
  • Save side: DiskImageFileStorage.save cached the caller's own object and replaced its info. A node that saves several frames from one canvas in a loop got every cached entry pointing at the last frame until eviction, while the PNGs on disk were correct.

Both paths now hand out / cache an independent copy. The conversion-failure warning and the fallback to the original mode are unchanged.

To pin the custom-node contract these paths serve, the PR adds offline compatibility tests that run six unmodified third-party PIL nodes from JPPhoto/cinematic-effects-pack (MIT, pinned to 4a4dfa77ec8c57968281f8162a685d1d89a1aaea) through the real invocation image interface, real image services, in-memory SQLite and disk storage under tmp_path: Chromatic Aberration, Lens Vignette, Lens Blur, Curves, Film Grain and Spherical Distortion, RGB and RGBA each, plus L and LA for Curves. The vendored files are byte-identical to upstream, kept with LICENSE and PROVENANCE.md under tests/app/invocations/third_party_cinematic_effects/, and excluded from ruff. The pack is registered under its own module name and unregistered after the module, so nothing leaks into other test files.

Upstream node behavior is recorded as baseline, not changed: Lens Blur and Film Grain return opaque RGB(A), Lens Blur needs an aperture image despite its optional declaration, Curves widens L/LA to RGB/RGBA.

Related Issues / Discussions

None.

QA Instructions

  • uv run --no-sync pytest tests/app/services/shared/test_invocation_context_images.py tests/app/services/image_files/test_image_files_disk.py tests/app/invocations/test_third_party_image_nodes.py — each file passes alone (30, 68 + 10 skipped, 17).
  • uv run --no-sync pytest -n 4 tests/app/services/images tests/app/services/image_files tests/app/routers/test_custom_nodes.py tests/test_nodes.py tests/app/services/shared/test_invocation_context_images.py tests/app/invocations/test_third_party_image_nodes.py — 213 passed, 11 skipped.
  • The LAB→L test and the save-isolation test fail without the respective fix.
  • Cost of the added copy on save, RGBA noise, 4 samples: 3.5–5.5 ms at 2048² against 271–301 ms PNG encode (compress level 1); 14–25 ms at 4096² against 1.09–1.22 s. About 1–1.5 % of the encode.
  • ruff check . and ruff format --check . clean.

Review

Resolved: the save-side aliasing was found in review and fixed here; the fixture's collision guard now checks all eight invocation types the pack registers (Film Grain registers three); an assertion that could not fail and a duplicate mocked test were removed. No remaining known risks; the only cost is one image copy per save.

Checklist

  • The PR has a short but descriptive title, suitable for a changelog
  • Meaningful regression coverage added / updated where needed; obsolete tests/code removed
  • Persisted-state and API changes include required migrations / compatibility validation
  • Relevant performance/efficiency opportunities considered; material claims have evidence
  • Material review findings resolved and relevant checks rerun
  • Documentation added / updated (if applicable)
  • Updated What's New copy (if doing a release after this PR)

Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.

get_pil now copies when a mode conversion fails, and disk storage caches its own copy on save instead of the caller's object.
Adds offline compatibility tests running six unmodified third-party PIL nodes (JPPhoto cinematic-effects-pack, MIT) through real image services.
@github-actions github-actions Bot added python PRs that change python files Root services PRs that change app services python-tests PRs that change python tests python-deps PRs that change python dependencies labels Nov 3, 2026
save now replaces cached entries for the image and its thumbnail and bumps the cache generation, so an overwrite is visible to later reads and a read that started before it cannot cache the old file.
@JPPhoto
JPPhoto requested a review from joshistoast November 3, 2026 17:39
@joshistoast
joshistoast enabled auto-merge (squash) November 3, 2026 22:11
@joshistoast
joshistoast merged commit e28937b into invoke-ai:main Nov 3, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python PRs that change python files python-deps PRs that change python dependencies python-tests PRs that change python tests Root services PRs that change app services

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants