Skip to content

Commit 9a5a20c

Browse files
reset graph cache in live query on collection gc (#419)
Co-authored-by: Sam Willis <[email protected]>
1 parent 77830e5 commit 9a5a20c

File tree

3 files changed

+37
-0
lines changed

3 files changed

+37
-0
lines changed

.changeset/ready-wasps-search.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@tanstack/db": patch
3+
---
4+
5+
Ensure that a new d2 graph is used for live queries that are cleaned up by the gc process. Fixes the "Graph already finalized" error.

packages/db/src/query/live-query-collection.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,13 @@ export function liveQueryCollectionOptions<
625625
// Return the unsubscribe function
626626
return () => {
627627
unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe())
628+
629+
// Reset caches so a fresh graph/pipeline is compiled on next start
630+
// This avoids reusing a finalized D2 graph across GC restarts
631+
graphCache = undefined
632+
inputsCache = undefined
633+
pipelineCache = undefined
634+
collectionWhereClausesCache = undefined
628635
}
629636
},
630637
}

packages/db/tests/query/live-query-collection.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,4 +288,29 @@ describe(`createLiveQueryCollection`, () => {
288288
expect(liveQuery.size).toBe(3) // Alice, Charlie, and David are active
289289
expect(liveQuery.get(4)).toEqual({ id: 4, name: `David`, active: true })
290290
})
291+
292+
it(`should not reuse finalized graph after GC cleanup (resubscribe is safe)`, async () => {
293+
const liveQuery = createLiveQueryCollection({
294+
query: (q) =>
295+
q
296+
.from({ user: usersCollection })
297+
.where(({ user }) => eq(user.active, true)),
298+
gcTime: 1,
299+
})
300+
301+
const unsubscribe = liveQuery.subscribeChanges(() => {})
302+
await liveQuery.preload()
303+
expect(liveQuery.status).toBe(`ready`)
304+
305+
// Unsubscribe and wait for GC to run and cleanup to complete
306+
unsubscribe()
307+
const deadline = Date.now() + 500
308+
while (liveQuery.status !== `cleaned-up` && Date.now() < deadline) {
309+
await new Promise((r) => setTimeout(r, 1))
310+
}
311+
expect(liveQuery.status).toBe(`cleaned-up`)
312+
313+
// Resubscribe should not throw (would throw "Graph already finalized" without the fix)
314+
expect(() => liveQuery.subscribeChanges(() => {})).not.toThrow()
315+
})
291316
})

0 commit comments

Comments
 (0)