fix(graphile-cache): prevent double-disposal of PostGraphile instances during shutdown#918
Open
pyramation wants to merge 2 commits intomainfrom
Open
fix(graphile-cache): prevent double-disposal of PostGraphile instances during shutdown#918pyramation wants to merge 2 commits intomainfrom
pyramation wants to merge 2 commits intomainfrom
Conversation
…s during shutdown Replace key-based disposedKeys Set with entry-based WeakSet to track disposed PostGraphile instances. The previous approach used a string key guard with a finally block that deleted the key after disposal completed. This caused a race condition in closeAllCaches(): 1. closeAllCaches() manually calls disposeEntry() which adds key to disposedKeys, releases pgl, then deletes key in finally block 2. graphileCache.clear() triggers LRU dispose callback for same entry 3. Key is no longer in disposedKeys, so disposeEntry() tries to release the already-released PostGraphile instance 4. PostGraphile throws 'instance has been released' Using a WeakSet keyed on the entry object reference: - Prevents double-disposal without needing cleanup in finally block - Avoids key-reuse edge cases (same cache key, different entry object) - Allows GC to clean up naturally when entries are no longer referenced
Contributor
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Adds unit tests that verify: - pgl.release() is called exactly once per entry during closeAllCaches() - closeAllCaches() does not throw when LRU dispose callback fires after manual disposal (the original bug scenario) - Empty cache is handled gracefully - Single LRU eviction disposes exactly once
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.
Summary
Fixes the
PostGraphile instance has been releasederror during test teardown (and anycloseAllCachescall) by replacing thedisposedKeysstring-keyedSetwith aWeakSet<GraphileCacheEntry>keyed on entry object references.Root cause:
disposeEntry()added the cache key todisposedKeysas a double-disposal guard, but then deleted it in thefinallyblock after disposal completed. WhencloseAllCaches()subsequently calledgraphileCache.clear(), the LRU dispose callback re-invokeddisposeEntry()for the same entry — but the guard key was already gone, so it attemptedpgl.release()on an already-released instance.Fix: Track disposal by entry object reference (
WeakSet) instead of key string. This eliminates both the race (nofinallycleanup needed) and a theoretical key-reuse edge case where different entry objects could share the same cache key string.Updates since last revision
Added a regression test suite in
graphile/graphile-cache/__tests__/graphile-cache.test.tsthat:pgl.release()to throw on double-call (matching real PostGraphile behavior)closeAllCaches()callsrelease()exactly once per entry, not twiceCI
server-testlogs were also checked — theError disposing PostGraphilemessage no longer appears in teardown output.Review & Testing Checklist for Human
closeAllCachesflow to confirm the race is resolved:disposeEntrymarks the entry in theWeakSet→Promise.allSettledcompletes →graphileCache.clear()triggers LRU dispose callback →disposeEntrysees entry inWeakSetand no-opsfinallyblock is safe — theWeakSetneeds no manual cleanup (entries are GC'd when unreferenced), unlike the oldSet<string>which leaked stale keys without thefinally. Note: ifdisposeEntryerrors partway through, the entry stays in the WeakSet, preventing a retry — confirm this is acceptable (a failed release would likely fail again anyway)server-testsuite (cd graphql/server-test && pnpm test) and confirm theError disposing PostGraphilelog no longer appears in outputNotes
pg-cacheand@pgpmjs/loggerto run in isolation; the full integration path is covered by the existingserver-testCI jobgraphile-cache.ts(fix, net −1 line) and newgraphile-cache.test.ts(regression test)Link to Devin session: https://app.devin.ai/sessions/c36c53a01f67404eb07113d24a4b7c67
Requested by: @pyramation