Skip to content

Commit

Permalink
Reactivate reactive variables when InMemoryCache gets new watchers.
Browse files Browse the repository at this point in the history
Fixes a bug introduced by #7279, which was triggered by calling
this.cancel() before re-watching in the QueryInfo#updateWatch method,
causing cache.watches.size to drop to zero temporarily, thereby invoking
forgetCache(cache). When this happened, reactive variables would stop
broadcasting updates to the forgotten cache, even though the unwatching
was only momentary, and would not begin broadcasting again until the
association was reestablished by other means.

This new implementation uses a WeakMap to remember the association between
caches and sets of reactive variables, which makes it possible to recall
those associations later, provided the cache has not been garbage
collected in the meantime.
  • Loading branch information
benjamn committed Feb 4, 2021
1 parent 6ab2fca commit c2e50a9
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 13 deletions.
15 changes: 14 additions & 1 deletion src/cache/inmemory/inMemoryCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
import { StoreReader } from './readFromStore';
import { StoreWriter } from './writeToStore';
import { EntityStore, supportsResultCaching } from './entityStore';
import { makeVar, forgetCache } from './reactiveVars';
import { makeVar, forgetCache, recallCache } from './reactiveVars';
import {
defaultDataIdFromObject,
PossibleTypesMap,
Expand Down Expand Up @@ -194,6 +194,19 @@ export class InMemoryCache extends ApolloCache<NormalizedCacheObject> {
}

public watch(watch: Cache.WatchOptions): () => void {
if (!this.watches.size) {
// In case we previously called forgetCache(this) because
// this.watches became empty (see below), reattach this cache to any
// reactive variables on which it previously depended. It might seem
// paradoxical that we're able to recall something we supposedly
// forgot, but the point of calling forgetCache(this) is to silence
// useless broadcasts while this.watches is empty, and to allow the
// cache to be garbage collected. If, however, we manage to call
// recallCache(this) here, this cache object must not have been
// garbage collected yet, and should resume receiving updates from
// reactive variables, now that it has a watcher to notify.
recallCache(this);
}
this.watches.add(watch);
if (watch.immediate) {
this.maybeBroadcastWatch(watch);
Expand Down
27 changes: 15 additions & 12 deletions src/cache/inmemory/reactiveVars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,20 @@ const varsByCache = new WeakMap<ApolloCache<any>, Set<ReactiveVar<any>>>();

export function forgetCache(cache: ApolloCache<any>) {
const vars = varsByCache.get(cache);
if (vars) {
consumeAndIterate(vars, rv => rv.forgetCache(cache));
varsByCache.delete(cache);
}
if (vars) vars.forEach(rv => rv.forgetCache(cache));
}

// Calling forgetCache(cache) serves to silence broadcasts and allows the
// cache to be garbage collected. However, the varsByCache WeakMap
// preserves the set of reactive variables that were previously associated
// with this cache, which makes it possible to "recall" the cache at a
// later time, by reattaching it to those variables. If the cache has been
// garbage collected in the meantime, because it is no longer reachable,
// you won't be able to call recallCache(cache), and the cache will
// automatically disappear from the varsByCache WeakMap.
export function recallCache(cache: ApolloCache<any>) {
const vars = varsByCache.get(cache);
if (vars) vars.forEach(rv => rv.attachCache(cache));
}

export function makeVar<T>(value: T): ReactiveVar<T> {
Expand Down Expand Up @@ -86,14 +96,7 @@ export function makeVar<T>(value: T): ReactiveVar<T> {
return rv;
};

rv.forgetCache = cache => {
const deleted = caches.delete(cache);
if (deleted) {
const vars = varsByCache.get(cache);
if (vars) vars.delete(rv);
}
return deleted;
};
rv.forgetCache = cache => caches.delete(cache);

return rv;
}
Expand Down

0 comments on commit c2e50a9

Please sign in to comment.