Skip to content

Commit e1bc8d3

Browse files
Reimlement gcManager
1 parent 38452f7 commit e1bc8d3

19 files changed

+123
-172
lines changed

packages/query-core/src/__tests__/gcManager.test.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ describe('gcManager', () => {
4040

4141
// Query exists and GC should not be running yet (query is active)
4242
expect(queryClient.getQueryCache().find({ queryKey: key })).toBeDefined()
43+
console.log(
44+
'gcManager.getEligibleItemCount()',
45+
gcManager.getEligibleItemCount(),
46+
)
4347
expect(gcManager.getEligibleItemCount()).toBe(0)
4448

4549
// Unsubscribe - this should mark the query for GC

packages/query-core/src/__tests__/mutationCache.test.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,6 @@ describe('mutationCache', () => {
342342
},
343343
1,
344344
)
345-
346345
await vi.advanceTimersByTimeAsync(10)
347346

348347
expect(testCache.getAll()).toHaveLength(1)
@@ -414,13 +413,10 @@ describe('mutationCache', () => {
414413
const unsubscribe = observer.subscribe(() => undefined)
415414

416415
observer.mutate(1)
417-
await vi.advanceTimersByTimeAsync(10)
418-
expect(queryClient.getMutationCache().getAll()).toHaveLength(1)
419416

420417
unsubscribe()
421418

422-
await vi.advanceTimersByTimeAsync(10)
423-
419+
await vi.advanceTimersByTimeAsync(11)
424420
expect(queryClient.getMutationCache().getAll()).toHaveLength(0)
425421
expect(onSuccess).toHaveBeenCalledTimes(1)
426422
})

packages/query-core/src/__tests__/mutationObserver.test.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ describe('mutationObserver', () => {
5858

5959
unsubscribe()
6060

61-
await vi.advanceTimersByTimeAsync(20)
62-
61+
await vi.advanceTimersByTimeAsync(10)
6362
expect(queryClient.getMutationCache().findAll()).toHaveLength(0)
6463
})
6564

@@ -80,7 +79,7 @@ describe('mutationObserver', () => {
8079

8180
mutation.reset()
8281

83-
await vi.advanceTimersByTimeAsync(20)
82+
await vi.advanceTimersByTimeAsync(10)
8483
expect(queryClient.getMutationCache().findAll()).toHaveLength(0)
8584

8685
unsubscribe()

packages/query-core/src/__tests__/mutations.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,7 @@ describe('mutations', () => {
781781

782782
// Verify mutation returns its own result, not callback returns
783783
expect(mutationResult).toBe('actual-result')
784+
console.log(results)
784785
expect(results).toEqual([
785786
'sync-onMutate',
786787
'async-onSuccess',

packages/query-core/src/__tests__/query.test.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,9 @@ describe('query', () => {
557557
const unsubscribe1 = observer.subscribe(() => undefined)
558558
unsubscribe1()
559559

560-
await vi.advanceTimersByTimeAsync(1)
560+
await vi.advanceTimersByTimeAsync(0)
561+
await vi.advanceTimersByTimeAsync(0)
562+
561563
expect(queryCache.find({ queryKey: key })).toBeUndefined()
562564
const unsubscribe2 = observer.subscribe(() => undefined)
563565
unsubscribe2()
@@ -579,7 +581,7 @@ describe('query', () => {
579581
expect(queryCache.find({ queryKey: key })).toBeDefined()
580582
unsubscribe()
581583

582-
await vi.advanceTimersByTimeAsync(1)
584+
await vi.advanceTimersByTimeAsync(0)
583585
expect(queryCache.find({ queryKey: key })).toBeUndefined()
584586
})
585587

packages/query-core/src/__tests__/queryClient.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,7 @@ describe('queryClient', () => {
700700
})
701701
await vi.advanceTimersByTimeAsync(10)
702702
await expect(promise).resolves.toEqual(1)
703-
await vi.advanceTimersByTimeAsync(10)
703+
await vi.advanceTimersByTimeAsync(1)
704704
expect(queryClient.getQueryData(key1)).toEqual(undefined)
705705
})
706706

packages/query-core/src/gcManager.ts

Lines changed: 51 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -40,43 +40,30 @@ export class GCManager {
4040
#forceDisable = false
4141
#eligibleItems = new Set<Removable>()
4242
#scheduledScanTimeoutId: ManagedTimerId | null = null
43-
#scheduledScanTimeout: number | null = null
44-
#scheduledScanIdleCallbackId: ManagedTimerId | null = null
43+
#isScheduledScan = false
4544

4645
constructor(config: GCManagerConfig = {}) {
4746
this.#forceDisable = config.forceDisable ?? false
4847
}
4948

5049
#scheduleScan(): void {
51-
if (this.#forceDisable) {
50+
if (this.#forceDisable || this.#isScheduledScan) {
5251
return
5352
}
5453

55-
if (this.#scheduledScanIdleCallbackId !== null) {
56-
return
57-
}
58-
59-
if (this.#isScanning && this.#scheduledScanTimeoutId !== null) {
60-
timeoutManager.clearTimeout(this.#scheduledScanTimeoutId)
61-
this.#isScanning = false
62-
this.#scheduledScanTimeoutId = null
63-
this.#scheduledScanTimeout = null
64-
this.#scheduledScanIdleCallbackId = null
65-
}
54+
this.#isScheduledScan = true
6655

67-
this.#scheduledScanIdleCallbackId = timeoutManager.setTimeout(() => {
68-
this.#scheduledScanIdleCallbackId = null
56+
queueMicrotask(() => {
57+
if (!this.#isScheduledScan) {
58+
return
59+
}
6960

70-
const now = Date.now()
61+
this.#isScheduledScan = false
7162

7263
let minTimeUntilGc = Infinity
7364

7465
for (const item of this.#eligibleItems) {
75-
const gcAt = item.getGcAtTimestamp()
76-
if (gcAt === null) {
77-
continue
78-
}
79-
const timeUntilGc = Math.max(0, gcAt - now)
66+
const timeUntilGc = getTimeUntilGc(item)
8067

8168
if (timeUntilGc < minTimeUntilGc) {
8269
minTimeUntilGc = timeUntilGc
@@ -87,20 +74,14 @@ export class GCManager {
8774
return
8875
}
8976

90-
if (this.#scheduledScanTimeout === minTimeUntilGc) {
91-
return
92-
}
93-
9477
if (this.#scheduledScanTimeoutId !== null) {
9578
timeoutManager.clearTimeout(this.#scheduledScanTimeoutId)
96-
this.#scheduledScanTimeoutId = null
9779
}
9880

81+
this.#isScanning = true
9982
this.#scheduledScanTimeoutId = timeoutManager.setTimeout(() => {
10083
this.#isScanning = false
10184
this.#scheduledScanTimeoutId = null
102-
this.#scheduledScanTimeout = null
103-
this.#scheduledScanIdleCallbackId = null
10485

10586
this.#performScan()
10687

@@ -109,26 +90,23 @@ export class GCManager {
10990
this.#scheduleScan()
11091
}
11192
}, minTimeUntilGc)
112-
113-
this.#isScanning = true
114-
this.#scheduledScanTimeout = minTimeUntilGc
115-
}, 0)
93+
})
11694
}
11795

11896
/**
11997
* Stop periodic scanning. Safe to call multiple times.
12098
*/
12199
stopScanning(): void {
100+
this.#isScanning = false
101+
this.#isScheduledScan = false
102+
122103
if (this.#scheduledScanTimeoutId === null) {
123104
return
124105
}
125106

126107
timeoutManager.clearTimeout(this.#scheduledScanTimeoutId)
127108

128-
this.#isScanning = false
129109
this.#scheduledScanTimeoutId = null
130-
this.#scheduledScanTimeout = null
131-
this.#scheduledScanIdleCallbackId = null
132110
}
133111

134112
/**
@@ -145,12 +123,17 @@ export class GCManager {
145123
* @param item - The query or mutation marked for GC
146124
*/
147125
trackEligibleItem(item: Removable): void {
148-
this.#eligibleItems.add(item)
126+
if (this.#forceDisable) {
127+
return
128+
}
149129

150-
// Start scanning if we have eligible items and aren't already scanning
151-
if (!this.#isScanning) {
152-
this.#scheduleScan()
130+
if (this.#eligibleItems.has(item)) {
131+
return
153132
}
133+
134+
this.#eligibleItems.add(item)
135+
136+
this.#scheduleScan()
154137
}
155138

156139
/**
@@ -160,11 +143,22 @@ export class GCManager {
160143
* @param item - The query or mutation no longer eligible for GC
161144
*/
162145
untrackEligibleItem(item: Removable): void {
146+
if (this.#forceDisable) {
147+
return
148+
}
149+
150+
if (!this.#eligibleItems.has(item)) {
151+
return
152+
}
153+
163154
this.#eligibleItems.delete(item)
164155

165-
// Stop scanning if no items are eligible
166-
if (this.getEligibleItemCount() === 0 && this.#isScanning) {
167-
this.stopScanning()
156+
if (this.isScanning()) {
157+
if (this.getEligibleItemCount() === 0) {
158+
this.stopScanning()
159+
} else {
160+
this.#scheduleScan()
161+
}
168162
}
169163
}
170164

@@ -183,7 +177,7 @@ export class GCManager {
183177
const wasCollected = item.optionalRemove()
184178

185179
if (wasCollected) {
186-
this.untrackEligibleItem(item)
180+
this.#eligibleItems.delete(item)
187181
}
188182
}
189183
} catch (error) {
@@ -194,4 +188,17 @@ export class GCManager {
194188
}
195189
}
196190
}
191+
192+
clear(): void {
193+
this.#eligibleItems.clear()
194+
this.stopScanning()
195+
}
196+
}
197+
198+
function getTimeUntilGc(item: Removable): number {
199+
const gcAt = item.getGcAtTimestamp()
200+
if (gcAt === null) {
201+
return Infinity
202+
}
203+
return Math.max(0, gcAt - Date.now())
197204
}

packages/query-core/src/mutation.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -98,20 +98,17 @@ export class Mutation<
9898
>
9999
#mutationCache: MutationCache
100100
#retryer?: Retryer<TData>
101-
#gcManager: GCManager
102101

103102
constructor(
104103
config: MutationConfig<TData, TError, TVariables, TOnMutateResult>,
105104
) {
106105
super()
107106

108107
this.#client = config.client
109-
this.#gcManager = config.client.getGcManager()
110108
this.mutationId = config.mutationId
111109
this.#mutationCache = config.mutationCache
112110
this.#observers = []
113111
this.state = config.state || getDefaultState()
114-
115112
this.setOptions(config.options)
116113
this.markForGc()
117114
}
@@ -129,7 +126,7 @@ export class Mutation<
129126
}
130127

131128
protected getGcManager(): GCManager {
132-
return this.#gcManager
129+
return this.#client.getGcManager()
133130
}
134131

135132
addObserver(observer: MutationObserver<any, any, any, any>): void {
@@ -161,13 +158,21 @@ export class Mutation<
161158
})
162159
}
163160

161+
private isSafeToRemove(): boolean {
162+
return this.state.status !== 'pending' && this.#observers.length === 0
163+
}
164+
164165
optionalRemove(): boolean {
165-
if (!this.isSafeToRemove()) {
166-
return false
166+
if (!this.#observers.length) {
167+
if (this.state.status === 'pending') {
168+
this.markForGc()
169+
} else {
170+
this.#mutationCache.remove(this)
171+
return true
172+
}
167173
}
168174

169-
this.#mutationCache.remove(this)
170-
return true
175+
return false
171176
}
172177

173178
continue(): Promise<unknown> {
@@ -378,7 +383,6 @@ export class Mutation<
378383
}
379384
this.state = reducer(this.state)
380385

381-
// Check for immediate removal after state change
382386
if (this.isSafeToRemove()) {
383387
this.markForGc()
384388
}
@@ -394,10 +398,6 @@ export class Mutation<
394398
})
395399
})
396400
}
397-
398-
isSafeToRemove(): boolean {
399-
return this.state.status !== 'pending' && this.#observers.length === 0
400-
}
401401
}
402402

403403
export function getDefaultState<

packages/query-core/src/mutationCache.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -237,14 +237,6 @@ export class MutationCache extends Subscribable<MutationCacheListener> {
237237
),
238238
)
239239
}
240-
241-
/**
242-
* Destroy the cache and clean up resources
243-
*/
244-
destroy(): void {
245-
// Clean up all mutations
246-
this.clear()
247-
}
248240
}
249241

250242
function scopeFor(mutation: Mutation<any, any, any, any>) {

0 commit comments

Comments
 (0)