🐛 fix(windows): stop leaking memory on repeated folder lookups#507
Merged
gaborbernat merged 2 commits intoJul 15, 2026
Merged
Conversation
get_win_folder_via_ctypes defined a fresh _GUID Structure on every call. Each POINTER(_GUID) registered a new entry in ctypes._pointer_type_cache that was never released, so repeated calls leaked memory unboundedly. Build the _GUID class and DLL bindings once via functools.cache and reuse the resolver across calls. Fixes tox-dev#501
get_win_folder_from_registry left the opened key handle to be reclaimed by the garbage collector. Under frequent calls that relies on GC timing to release OS handles; use a context manager so the handle closes deterministically.
cf51027 to
5b52e66
Compare
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.
Looking up a Windows folder path in a loop leaked memory. A service that builds a cache path with
platformdirs.user_data_pathabout once a second grew by gigabytes of retained RAM over a few hours, as issue #501 reports. 🐛 Each call toget_win_folder_via_ctypesdefined a new_GUIDStructuresubclass, and everyPOINTER(_GUID)registered an entry inctypes._pointer_type_cachethat nothing released.The change builds the
_GUIDclass and the three DLL bindings once behindfunctools.cacheand runs a resolver closure over them on each call.get_win_folder_via_ctypeskeeps its signature and delegates to the cached builder, so the fallback selector and every caller stay the same. A second commit closes the registry-fallback key with a context manager rather than leaving the OS handle for the garbage collector to reclaim.I confirmed the fix by exercising the real function 10,000 times, stubbing only
WinDLLandsys.platformsoPOINTERandStructurestill run. On Python 3.13 the cache and the retained_GUIDclasses stop growing, and memray reports a large drop in peak memory. ✨ctypes._pointer_type_cache_GUIDclassesPython 3.14 already reworked
_pointer_type_cacheupstream, which hides the cache symptom on that version, while the retained-object growth it fixes is version-independent. Returned paths are unchanged.