question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Memory Leak on React Native iOS

See original GitHub issue

We notice a memory leak with the simplest usage of Recoil when running React Native with Expo on iOS.

The reproducing app is this:

import { registerRootComponent } from "expo";
import React, { useEffect, useRef } from "react";
import { Text } from "react-native";
import { atom, RecoilRoot, useRecoilState } from "recoil";

registerRootComponent(App);

function App() {
  return (
    <RecoilRoot>
      <MyComponent />
    </RecoilRoot>
  );
}

function MyComponent() {
  const [state, setState] = useRecoilState(myAtom);

  const updateState = () => setState(constructBigFatObject());

  useEffect(() => {
    const handle = setTimeout(updateState, 1);
    return () => clearTimeout(handle);
  }, [state]);

  const renderCount = useRef(0);
  return <Text>{renderCount.current++}</Text>;
}

const myAtom = atom({
  key: "myAtom",
  default: constructBigFatObject()
});

function constructBigFatObject() {
  const obj = {};
  for (let i = 0; i < 10000; i++) {
    obj[i.toString()] = i;
  }
  return obj;
}

Basically we repeatedly create big objects and set them as atom state. On Android, everything works fine, memory is constant, but on iOS, memory usage of the app keeps increasing forever (until OOM).

If we replace useRecoilState with a normal useState, this effect vanishes, so it is not a problem of the Garbage Collector not working at all, but for some reason, it is not working for Recoil States.

This is with both 0.2.0 and 0.3.1.

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:1
  • Comments:9 (2 by maintainers)

github_iconTop GitHub Comments

7reactions
xotahalcommented, May 28, 2021

Hey folks 👋 First, thank you for this amazing library! 👏

I want to share my findings with you. I added atomFamily and selectorFamily to @bearbytes code because I wanted to have some dependencies. To make it a little bit more complicated.

Here is the whole repo: https://github.com/xotahal/recoil-leak (just yarn, yarn start & yarn ios to run the app) The App.tsx is here: https://github.com/xotahal/recoil-leak/blob/master/App.tsx And here’s the simple recoil state I was using:

const myAtom = atomFamily({
  key: 'myAtom',
  default: null,
});
const versionState = atom({
  key: 'version',
  default: 0,
});

const mySelector = selectorFamily({
  key: 'selector',
  set:
    key =>
    ({set}, newValue) => {
      set(myAtom(key), newValue);
    },
  get:
    key =>
    ({get}) => {
      return get(myAtom(key));
    },
});

Then my test case was increment versionState and for each version create a new selector in mySelector family.

  const [version, setVersion] = useRecoilState(versionState);
  const [state, setState] = useRecoilState(mySelector(version));
  
  <Button
    title="Create new family selector"
    onPress={() => {
      setVersion(current => current + 1);
    }}
  />

I ran the app in a production build. Then I created 30 new selectors and took a memory snapshot. I found that every time when I created a new selector family recoil created a new state and kept the previous state. These are all version of states I had in memory snapshot.

Screen Shot 2021-05-28 at 5 26 25 PM Screen Shot 2021-05-28 at 4 48 46 PM (2)

Questions

  1. Why do we need to keep the whole history of states?
  2. Is there any way how we could turn this off?
  3. I’ve read a couple of issues where you guys are talking about GC. Is this something that GC will help with in future? If so, when do you think this will be available?
  4. Is there anything we can do to help to resolve this?
1reaction
ahfarmercommented, Jun 9, 2021

@drarmstr I can verify that this occurs in a production build. I’ve been testing exclusively in production builds. Production builds disable the debug atom state history, but selectors are currently retaining every input and every output for all time.

If you remove the selector from the example, and run in production mode, the memory leaks are substantially less. It’s a selector problem.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Solving Memory Leaks with React Native - Enquero
In essence, memory leaks can be defined as memory that is not required by an application anymore that for some reason is not...
Read more >
How to Improve React Native App Performance in 2023 Tips
Memory Leaks in React Native · Run React Native app normally (react-native run-android) · Run Android Studio · On the menu, click Tools...
Read more >
Hunting JS memory leaks in React Native apps
One of the most common reasons of performance issues are memory leaks. In this article, we gathered the typical approaches to debug and...
Read more >
Finding memory leaks react-native app (iOS) - Medium
Problem: Our react-native app was working well on all devices and except iPhone 6 it was resulting in a crash. After high level...
Read more >
How to find out memory leaks in react native app?
If you go to profiler, then you will see a graph of your memory, then you can see if the momory increases every...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found