Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/calm-goats-punch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/query-core': patch
---

Fix memory leak in infinite query by preventing duplicate abort event listeners
47 changes: 47 additions & 0 deletions packages/query-core/src/__tests__/infiniteQueryBehavior.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -489,4 +489,51 @@ describe('InfiniteQueryBehavior', () => {

unsubscribe()
})

test('should not register duplicate abort event listeners when signal is accessed multiple times', async () => {
const key = queryKey()
let signalAccessCount = 0
const listenerCounts: Array<number> = []

const queryFnSpy = vi.fn().mockImplementation(({ signal }) => {
signalAccessCount++

const originalAddEventListener = signal.addEventListener
let currentListenerCount = 0
signal.addEventListener = vi.fn((...args) => {
currentListenerCount++
return originalAddEventListener.apply(signal, args)
})

// Access signal multiple times to trigger getter
signal
signal
signal

listenerCounts.push(currentListenerCount)
signal.addEventListener = originalAddEventListener

return `page-${signalAccessCount}`
})

const observer = new InfiniteQueryObserver(queryClient, {
queryKey: key,
queryFn: queryFnSpy,
getNextPageParam: (_lastPage, pages) => {
return pages.length < 3 ? pages.length + 1 : undefined
},
initialPageParam: 1,
})

const unsubscribe = observer.subscribe(() => {})

await vi.advanceTimersByTimeAsync(0)

await observer.fetchNextPage()
await observer.fetchNextPage()

expect(listenerCounts.every((count) => count <= 1)).toBe(true)

unsubscribe()
})
})
14 changes: 10 additions & 4 deletions packages/query-core/src/infiniteQueryBehavior.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,22 @@ export function infiniteQueryBehavior<TQueryFnData, TError, TData, TPageParam>(

const fetchFn = async () => {
let cancelled = false
let listenerAttached = false
const addSignalProperty = (object: unknown) => {
Object.defineProperty(object, 'signal', {
enumerable: true,
get: () => {
if (context.signal.aborted) {
cancelled = true
} else {
context.signal.addEventListener('abort', () => {
cancelled = true
})
} else if (!listenerAttached) {
listenerAttached = true
context.signal.addEventListener(
'abort',
() => {
cancelled = true
},
{ once: true },
)
}
return context.signal
},
Expand Down