Repository navigation
Keep a shared texture alive while adding another with its name - #2978
Merged
Merged
Conversation
When a texture is added whose name another texture already has, the atlas reuses that texture's slot. If the garbage collector ran inside _add_texture_ref and freed the last other texture with the name, its finalizer popped the name and freed the slot, and the add raised KeyError. This failed reliably on Python 3.14 in CI when loading a tile map. _add now holds a strong reference to a live texture with the name while it adds the new one, and uses the full add path if none is alive. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the
KeyErrorthat made #2977 fail on Python 3.14 five times in a row:Cause
When a texture is added whose atlas name (image + transform) another texture already has,
_addtakes the fast path and shares that texture's slot:Say the only other texture with that name is garbage stuck in a reference cycle, but not collected yet. If the cycle collector runs inside
_add_texture_ref(itsWeakSet.addandfinalize()both allocate), that texture's finalizer drops the name's count to 0, pops the name, and frees its region and slot. The new texture then has no slot. It's the same family as #2955, but a different moment.Python 3.14's garbage collector runs at different times, which is presumably why only 3.14 hit it, and only once #2977 changed when sprites in a tile map are freed. I couldn't reproduce the CI failure itself here (Windows, Python 3.13 and 3.14 both pass), but the new test reproduces the same
KeyErrordeterministically.Fix
_addtakes a strong reference to a live texture from_unique_textures[name]and holds it while adding the new one, so that texture can't be collected in the middle. If no texture with the name is alive, it uses the full add path instead of the fast path.Test
test_add_while_last_shared_texture_is_collectedmakes a texture garbage inside a reference cycle, then runsgc.collect()at the moment_add_texture_refstarts. Without the fix it raises the sameKeyErroras CI. With it, the new texture gets the shared slot.The full test suite (1852), ruff, mypy and pyright pass. The atlas and tile map tests pass on Python 3.14 locally.
To check it against #2977 before either merges, I pushed a temporary branch with #2977 plus this fix and started the PyTest workflow on it. I'll delete that branch afterwards.
🤖 Generated with Claude Code