···11+---
22+'@urql/exchange-graphcache': patch
33+---
44+55+Add skipping of garbage collection runs when the cache is waiting for optimistic, deferred or other results in layers. This means that we only take an opportunity to run garbage collection after results have settled and are hence decreasing the chance of hogging the event loop when a run isn't needed.
···4141 queryRootKey: string;
4242 /** Number of references to each entity (except "Query") */
4343 refCount: KeyMap<number>;
4444- /** Number of references to each entity on optimistic layers */
4545- refLock: OperationMap<KeyMap<number>>;
4644 /** A map of entity fields (key-value entries per entity) */
4745 records: NodeMap<EntityField>;
4846 /** A map of entity links which are connections from one entity to another (key-value entries per entity) */
···5149 deferredKeys: Set<number>;
5250 /** A set of Query operation keys that are in-flight and awaiting a result */
5351 commutativeKeys: Set<number>;
5252+ /** A set of Query operation keys that have been written to */
5353+ dirtyKeys: Set<number>;
5454 /** The order of optimistic layers */
5555 optimisticOrder: number[];
5656 /** This may be a persistence adapter that will receive changes in a batch */
···160160 let i = data.optimisticOrder.length;
161161 while (
162162 --i >= 0 &&
163163- data.refLock.has(data.optimisticOrder[i]) &&
163163+ data.dirtyKeys.has(data.optimisticOrder[i]) &&
164164 data.commutativeKeys.has(data.optimisticOrder[i])
165165 )
166166 squashLayer(data.optimisticOrder[i]);
···175175 currentDebugStack.length = 0;
176176 }
177177178178- // Schedule deferred tasks if we haven't already
179179- if (process.env.NODE_ENV !== 'test' && !data.defer) {
180180- data.defer = true;
181181- setTimeout(() => {
182182- initDataState('read', data, null);
183183- gc();
184184- persistData();
185185- clearDataState();
186186- data.defer = false;
187187- });
178178+ if (process.env.NODE_ENV !== 'test') {
179179+ // Schedule deferred tasks if we haven't already, and if either a persist or GC run
180180+ // are likely to be needed
181181+ if (!data.defer && (data.storage || !data.optimisticOrder.length)) {
182182+ data.defer = true;
183183+ setTimeout(() => {
184184+ initDataState('read', data, null);
185185+ gc();
186186+ persistData();
187187+ clearDataState();
188188+ data.defer = false;
189189+ });
190190+ }
188191 }
189192};
190193···230233 persist: new Set(),
231234 queryRootKey,
232235 refCount: new Map(),
233233- refLock: new Map(),
234236 links: {
235237 optimistic: new Map(),
236238 base: new Map(),
···241243 },
242244 deferredKeys: new Set(),
243245 commutativeKeys: new Set(),
246246+ dirtyKeys: new Set(),
244247 optimisticOrder: [],
245248 storage: null,
246249});
···314317};
315318316319/** Adjusts the reference count of an entity on a refCount dict by "by" and updates the gc */
317317-const updateRCForEntity = (
318318- gc: undefined | Set<string>,
319319- refCount: KeyMap<number>,
320320- entityKey: string,
321321- by: number
322322-): void => {
320320+const updateRCForEntity = (entityKey: string, by: number): void => {
323321 // Retrieve the reference count and adjust it by "by"
324324- const count = refCount.get(entityKey) || 0;
325325- const newCount = count + by;
326326- refCount.set(entityKey, newCount);
322322+ const count = currentData!.refCount.get(entityKey) || 0;
323323+ const newCount = count + by > 0 ? count + by : 0;
324324+ currentData!.refCount.set(entityKey, newCount);
327325 // Add it to the garbage collection batch if it needs to be deleted or remove it
328326 // from the batch if it needs to be kept
329329- if (gc) {
330330- if (newCount <= 0) gc.add(entityKey);
331331- else if (count <= 0 && newCount > 0) gc.delete(entityKey);
332332- }
327327+ if (!newCount) currentData!.gc.add(entityKey);
328328+ else if (!count && newCount) currentData!.gc.delete(entityKey);
333329};
334330335331/** Adjusts the reference counts of all entities of a link on a refCount dict by "by" and updates the gc */
336336-const updateRCForLink = (
337337- gc: undefined | Set<string>,
338338- refCount: KeyMap<number>,
339339- link: Link | undefined,
340340- by: number
341341-): void => {
342342- if (typeof link === 'string') {
343343- updateRCForEntity(gc, refCount, link, by);
344344- } else if (Array.isArray(link)) {
345345- for (let i = 0, l = link.length; i < l; i++) {
346346- if (Array.isArray(link[i])) {
347347- updateRCForLink(gc, refCount, link[i], by);
348348- } else if (link[i]) {
349349- updateRCForEntity(gc, refCount, link[i] as string, by);
350350- }
351351- }
332332+const updateRCForLink = (link: Link | undefined, by: number): void => {
333333+ if (Array.isArray(link)) {
334334+ for (let i = 0, l = link.length; i < l; i++) updateRCForLink(link[i], by);
335335+ } else if (typeof link === 'string') {
336336+ updateRCForEntity(link, by);
352337 }
353338};
354339···391376392377/** Garbage collects all entities that have been marked as having no references */
393378export const gc = () => {
379379+ // If we're currently awaiting deferred results, abort GC run
380380+ if (currentData!.optimisticOrder.length) return;
381381+394382 // Iterate over all entities that have been marked for deletion
395383 // Entities have been marked for deletion in `updateRCForEntity` if
396384 // their reference count dropped to 0
397397- const { gc: batch } = currentData!;
398398- for (const entityKey of batch.keys()) {
399399- // Check first whether the reference count is still 0
385385+ for (const entityKey of currentData!.gc.keys()) {
386386+ // Remove the current key from the GC batch
387387+ currentData!.gc.delete(entityKey);
388388+389389+ // Check first whether the entity has any references,
390390+ // if so, we skip it from the GC run
400391 const rc = currentData!.refCount.get(entityKey) || 0;
401401- if (rc > 0) {
402402- batch.delete(entityKey);
403403- return;
404404- }
405405-406406- // Each optimistic layer may also still contain some references to marked entities
407407- for (const layerKey of currentData!.refLock.keys()) {
408408- const refCount = currentData!.refLock.get(layerKey);
409409- if (refCount) {
410410- const locks = refCount.get(entityKey) || 0;
411411- // If the optimistic layer has any references to the entity, don't GC it,
412412- // otherwise delete the reference count from the optimistic layer
413413- if (locks > 0) return;
414414- refCount.delete(entityKey);
415415- }
416416- }
392392+ if (rc > 0) continue;
417393418394 // Delete the reference count, and delete the entity from the GC batch
419395 currentData!.refCount.delete(entityKey);
420420- batch.delete(entityKey);
421396 currentData!.records.base.delete(entityKey);
422397 const linkNode = currentData!.links.base.get(entityKey);
423398 if (linkNode) {
424399 currentData!.links.base.delete(entityKey);
425425- for (const fieldKey in linkNode) {
426426- updateRCForLink(batch, currentData!.refCount, linkNode[fieldKey], -1);
427427- }
400400+ for (const fieldKey in linkNode) updateRCForLink(linkNode[fieldKey], -1);
428401 }
429402 }
430403};
···484457 fieldKey: string,
485458 link?: Link | undefined
486459) => {
487487- const data = currentData!;
488488- // Retrieve the reference counting dict or the optimistic reference locking dict
489489- let refCount: KeyMap<number> | undefined;
490490- // Retrive the link NodeMap from either an optimistic or the base layer
491491- let links: KeyMap<Dict<Link | undefined>> | undefined;
492492- // Set the GC batch if we're not optimistically updating
493493- let gc: undefined | Set<string>;
494494- if (currentOptimisticKey) {
495495- // The refLock counters are also reference counters, but they prevent
496496- // garbage collection instead of being used to trigger it
497497- refCount = data.refLock.get(currentOptimisticKey);
498498- if (!refCount)
499499- data.refLock.set(currentOptimisticKey, (refCount = new Map()));
500500- links = data.links.optimistic.get(currentOptimisticKey);
501501- } else {
502502- refCount = data.refCount;
503503- links = data.links.base;
504504- gc = data.gc;
460460+ // Retrieve the link NodeMap from either an optimistic or the base layer
461461+ const links = currentOptimisticKey
462462+ ? currentData!.links.optimistic.get(currentOptimisticKey)
463463+ : currentData!.links.base;
464464+ // Update the reference count for the link
465465+ if (!currentOptimisticKey) {
466466+ updateRCForLink(links?.get(entityKey)?.[fieldKey], -1);
467467+ updateRCForLink(link, 1);
505468 }
506506-507507- // Retrieve the previous link for this field
508508- const prevLinkNode = links && links.get(entityKey);
509509- const prevLink = prevLinkNode && prevLinkNode[fieldKey];
510510-511469 // Update persistence batch and dependencies
512470 updateDependencies(entityKey, fieldKey);
513471 updatePersist(entityKey, fieldKey);
514472 // Update the link
515515- setNode(data.links, entityKey, fieldKey, link);
516516- // First decrease the reference count for the previous link
517517- updateRCForLink(gc, refCount, prevLink, -1);
518518- // Then increase the reference count for the new link
519519- updateRCForLink(gc, refCount, link, 1);
473473+ setNode(currentData!.links, entityKey, fieldKey, link);
520474};
521475522476/** Reserves an optimistic layer and preorders it */
···538492 index = index > -1 ? index : 0;
539493 index < data.optimisticOrder.length &&
540494 !data.deferredKeys.has(data.optimisticOrder[index]) &&
541541- (!data.refLock.has(data.optimisticOrder[index]) ||
495495+ (!data.dirtyKeys.has(data.optimisticOrder[index]) ||
542496 !data.commutativeKeys.has(data.optimisticOrder[index]));
543497 index++
544498 );
···563517 data.optimisticOrder.unshift(layerKey);
564518 }
565519566566- if (!data.refLock.has(layerKey)) {
567567- data.refLock.set(layerKey, new Map());
520520+ if (!data.dirtyKeys.has(layerKey)) {
521521+ data.dirtyKeys.add(layerKey);
568522 data.links.optimistic.set(layerKey, new Map());
569523 data.records.optimistic.set(layerKey, new Map());
570524 }
···572526573527/** Clears all links and records of an optimistic layer */
574528const clearLayer = (data: InMemoryData, layerKey: number) => {
575575- if (data.refLock.has(layerKey)) {
576576- data.refLock.delete(layerKey);
529529+ if (data.dirtyKeys.has(layerKey)) {
530530+ data.dirtyKeys.delete(layerKey);
577531 data.records.optimistic.delete(layerKey);
578532 data.links.optimistic.delete(layerKey);
579533 data.deferredKeys.delete(layerKey);