Skip to content
This repository has been archived by the owner on Jul 10, 2019. It is now read-only.

Memory leak in removeClientSetsFromDocument #318

Open
AndreyChechel opened this issue Oct 24, 2018 · 0 comments
Open

Memory leak in removeClientSetsFromDocument #318

AndreyChechel opened this issue Oct 24, 2018 · 0 comments
Assignees

Comments

@AndreyChechel
Copy link

We've identified memory leak in removeClientSetsFromDocument function. The problem is the query parameter can refer to identical DocumentNodes which are actually different objects having different references. That makes cache hits impossible and results in the removed Map growing constantly.

Seems the problem relates to apollographql/apollo-client#3444 - this PR addresses absolutely the same issue in apollo-client repository.

Steps to reproduce

Our setup looks as the following - we have 2 servers: a Web server and an API server. The Web server communicates to the API server using Apollo client, the Web server also uses apollo-link-state to maintain some cache. Apollo client, cache object, links are created and initialized on the Web server for each client request and supposed to be fully released after a request is processed.

I prepared a small demo reproducing the problem:

  1. Create an empty project
  2. Add dependencies to package.json and run yarn install
    "apollo-cache-inmemory": "^1.3.6",
    "apollo-client": "^2.4.3",
    "apollo-link-state": "^0.4.2",
    "express": "^4.16.4",
    "graphql": "^14.0.2",
    "graphql-tag": "^2.10.0"
  1. Add index.js file
const express = require("express");
const gql = require("graphql-tag");
const ApolloClient = require("apollo-client").ApolloClient;
const InMemoryCache = require("apollo-cache-inmemory").InMemoryCache;
const withClientState = require("apollo-link-state").withClientState;

// Create Express server
var app = express();

app.get('/', async function (req, res) {

    // Cache object is unique for each request
    const cache = new InMemoryCache();

    // Initialize link state
    const link = withClientState({
        cache,
        defaults: {
            title: "",
        },
        resolvers: {
            Mutation: {
                updateTitle: (_, { title }, { cache }) => {
                    const data = { title };
                    cache.writeData({ data });
                    return null;
                },
            },
        },
    });

    // Initialize GraphQL client
    const client = new ApolloClient({
        cache,
        link
    });

    // Update App title in the local cache
    await client.mutate({
        variables: { title: "Test App" },
        mutation: gql`
            mutation UpdateTitle($title: String!) {
                updateTitle(title: $title) @client {
                    title
                }
            }`,
    });

    // Respond to the client. All allocated 
    // resources are supposed to be released once done.
    res.sendStatus(200);
});

// Start the server
app.listen(3000, function () {
});
  1. Run the application node --inspect=0.0.0.0:9229 ./index.js
  2. Use Chrome DevTools to attach to NodeJS process
  3. Load http://localhost:3000 page in a browser
  4. Take a heap snapshot in DevTools (you might want to run GC before)
  5. Reload the page
  6. Repeat steps 7-8 a few times
  7. Inspect snapshots, you'll notice Array objects increased and retained for each page reload
    image
  8. Look for a Map object named removed in Retainers panel. That Map has as many items, as many times the page was loaded. These items will never be removed - it's basically the reason of the memory leak issue.
    image
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

2 participants