Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memory Leak - InMemoryCache and Optimism never deletes records #7086

Closed
kamilkisiela opened this issue Sep 28, 2020 · 6 comments
Closed

Memory Leak - InMemoryCache and Optimism never deletes records #7086

kamilkisiela opened this issue Sep 28, 2020 · 6 comments

Comments

@kamilkisiela
Copy link
Contributor

In a project I work on (used by milions of people) we use Electron and in every pop-out window, we pass a reference to Window object within an operation's context. Because the callback holds a reference to QueryInfo => ObservableQuery => ObservableQuery.options => options.context => context.window we have a memory leak.
Consumers open new windows every few minutes (it's a chat app) which adds more and more instances of Window.

How to reproduce the issue:

Repository | Deployed app

Here, we use dep from optimism package that creates a Map:

private watchDep = dep<Cache.WatchOptions>();

When there's a new query, InMemoryCache runs broadcastWatch method in which it uses optimism.
The watchDep.dirty(c) removes a record from the Map

this.watchDep.dirty(c);

Later on in broadcastWatch, the watchDep(c) is called and it sets a new record in our Map:

The problem is that c which represents Cache.WatchOptions

export interface WatchOptions extends ReadOptions {
immediate?: boolean;
callback: WatchCallback;
}

is stored forever in the Map created in Optimism's dep() and that callback is function that depends on QueryInfo class

this.cancel = this.cache.watch(this.lastWatch = {
query: this.document!,
variables,
optimistic: true,
callback: diff => this.setDiff(diff),
});

which depends directly on ObservableQuery class in which we store query's options operation's context.

Versions

  System:
    OS: macOS 10.15.6
  Binaries:
    Node: 10.18.0 - ~/.nvm/versions/node/v10.18.0/bin/node
    Yarn: 1.22.4 - ~/.yarn/bin/yarn
    npm: 6.13.4 - ~/.nvm/versions/node/v10.18.0/bin/npm
  Browsers:
    Chrome: 85.0.4183.121
    Edge: 85.0.564.63
    Firefox: 79.0
    Safari: 13.1.2
  npmPackages:
    @apollo/client: 3.2.0 => 3.2.0
@kamilkisiela
Copy link
Contributor Author

@benjamn I'm not entirely sure how all of that works but I can help to fix it, just need few clues :)

@kamilkisiela
Copy link
Contributor Author

I believe we store callback in multiple places, not only in InMemoryCache.watchDep

@smykhailov
Copy link

#7013 Might be related to this

@benjamn
Copy link
Member

benjamn commented Sep 29, 2020

The optimism wrapper system does use a bounded LRU cache for every wrapped function, so the amount of cached data cannot grow indefinitely, but the default size is pretty large.

@dotansimha
Copy link
Contributor

dotansimha commented Oct 10, 2020

@benjamn I believe this issue should get more attention. Since we migrated a huge project to v3, we see multiple memory leaks.
The one @kamilkisiela demonstrated above is only one, we are chasing those and it's not easy to find. Here's an example for a heap snapshot. It's using Electron and opens a new Window. Eventually, we have Detached Window(s) since Apollo keeps records of the query params in memory, and never deletes it.

image

Here's another heap snapshot of a memory leak we chase, and caused by retained context from observableQuery:

image

I think the 2 issues above might be related, and caused by the same issue, and @kamilkisiela 's issue is just one symptom of it. Both seems to be related to watchDep and the way it keeps it's records.

@benjamn
Copy link
Member

benjamn commented Oct 13, 2020

Thanks to @dotansimha's reproduction and @kamilkisiela's proposed solution, I'm confident this leak is fixed in @apollo/client@3.2.4 and @apollo/client@3.3.0-beta.12. Thanks for digging into these implementation details and correctly identifying the underlying problem, @kamilkisiela!

@benjamn benjamn closed this as completed Oct 13, 2020
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 15, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants