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

JS Heap grows when using subscriptions #6880

Closed
H-a-w-k opened this issue Aug 22, 2020 · 5 comments
Closed

JS Heap grows when using subscriptions #6880

H-a-w-k opened this issue Aug 22, 2020 · 5 comments

Comments

@H-a-w-k
Copy link

H-a-w-k commented Aug 22, 2020

Intended outcome:

When I subscribe, I expect the memory to stay somewhat constant, since the subscription only updates data, and does not add more elements.

Actual outcome:

As soon as I use useSubscription or subscribeToMore the js heap starts building up.

When using the apollo dev tools, I can see that the cache is not growing, and it behaves as expected.
It's like the garbage collector doesn't get everything, and somewhere a the subscribed data is persisted.

This simple subscription causes the problem. To get a bigger leak, I've added a picture where I have used the subscription many times.

A repo with the test files can be found here:
https://github.com/H-a-w-k/apollo-subscription-heap-growing

Client code:

import React, { useEffect } from "react";
import { WebSocketLink } from "@apollo/client/link/ws";
import {
  split,
  HttpLink,
  InMemoryCache,
  ApolloClient,
  gql,
  useQuery,
  ApolloProvider,
} from "@apollo/client";
import { getMainDefinition } from "@apollo/client/utilities";

const httpLink = new HttpLink({
  uri: "http://localhost:4000/",
});
const wsLink = new WebSocketLink({
  uri: "ws://localhost:4000/graphql",
  options: {
    reconnect: true,
  },
});

const link = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === "OperationDefinition" &&
      definition.operation === "subscription"
    );
  },
  wsLink,
  httpLink
);

const cache = new InMemoryCache({
  typePolicies: {
    Subscription: {
      fields: {
        booksUpdated: {
          merge: false,
        },
      },
    },
    Book: {
      keyFields: ["id"],
    },
  },
});

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

const query = gql`
  query booksQuery {
    books {
      id
      title
    }
  }
`;

const subscription = gql`
  subscription booksSubscription {
    books: booksUpdated {
      id
      title
    }
  }
`;

const MemoryLeakFinder = () => {
  const { data, subscribeToMore } = useQuery(query);

  useEffect(() => {
    subscribeToMore({
      document: subscription,
    });
    //Add for steeper curve
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    subscribeToMore({
      document: subscription,
    });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return <p>Looking for leaks!! {data?.books?.[0]?.title}</p>;
};

function App() {
  return (
    <ApolloProvider client={client}>
      <MemoryLeakFinder></MemoryLeakFinder>
    </ApolloProvider>
  );
}

export default App;

Server code:

const { PubSub, ApolloServer, gql } = require("apollo-server");

const pubsub = new PubSub();
const typeDefs = gql`
  type Book {
    id: Int
    title: String
    author: String
  }
  type Query {
    books: [Book]
  }
  type Subscription {
    booksUpdated: [Book]
  }
`;
const books = [
  {
    id: 1,
    title: "Harry Potter and the Chamber of Secrets",
    author: "J.K. Rowling",
  },
  {
    id: 2,
    title: "Jurassic Park",
    author: "Michael Crichton",
  },
];

const BOOKS_UPDATED = "BOOKS_UPDATED";

let i = 1;

setInterval(() => {
  books[0].title = "Harry Potter and the Chamber of Secrets " + i++;
  pubsub.publish(BOOKS_UPDATED, { booksUpdated: books });
}, 500);

const resolvers = {
  Query: {
    books: () => books,
  },
  Subscription: {
    booksUpdated: {
      subscribe: () => pubsub.asyncIterator([BOOKS_UPDATED]),
    },
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: async ({ req, connection }) => {
    if (connection) {
      // check connection for metadata
      return connection.context;
    } else {
      // check from req
      const token = req.headers.authorization || "";

      return { token };
    }
  },
});

server.listen().then(({ url, subscriptionsUrl }) => {
  console.log(`🚀 Server ready at ${url}`);
  console.log(`🚀 Subscriptions ready at ${subscriptionsUrl}`);
});

How to reproduce the issue:

Use the repo with the test files can be found here:
https://github.com/H-a-w-k/apollo-subscription-heap-growing

or just copy paste the code above and install necessary dependencies.
Just start the client and server and start viewing the result in task manager and profiler

apollo cache 1
apollo cache 2
HeapFromTestProject

@michal-damiecki
Copy link

Hey @brainkim, any news about this issue? In our project, we've experienced the same problem.

@etodanik
Copy link

Experiencing the same exact thing on React Native, very severely

@phryneas
Copy link
Member

phryneas commented May 31, 2023

I just did a 7 minute-sampling with the example given (but an update every 10ms, not 500, so I'd expect to see it even more), but I'm not seeing a significant heap size increase.
I could understand this happening in React Native (as I already explained to @israelidanny in #7775 (comment) - please don't spam!), but I can't make sense of this in modern browsers.

@H-a-w-k, @michal-damiecki, could you give a bit more context on the environments where you were experiencing these issues?

@phryneas
Copy link
Member

phryneas commented Dec 22, 2023

Since there hasn't been any more feedback for half a year and we cannot reproduce it ourselves, I'm closing the issue.

At the same time I want to bring it to your attention that the upcoming version 3.9 (out in beta right now) will contain a lot of memory fixes: See the announcement blog post.

If this comes up again (and has not been fixed by 3.9), please open a new issue with a new reproduction. Thank you!

Copy link
Contributor

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
For general questions, we recommend using StackOverflow or our discord server.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jan 22, 2024
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

6 participants