Open pothos-dev opened 3 years ago
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.
Thanks for the reproduction repo and the thorough explanation @xotahal I came here exploring if I can use recoil in a mobile app. But this seems like a blocker. Does this happen in mobile web in iOS? in react native using Hermes in iOS? This seems related to the JavaScript engine. I still need to dig deeper to make a decision
It may be you are looking at the retention of debug states in the development build. Does this repro in the production version?
Thanks for getting back to us. I used this to build the app. Production with "Debug executable" on. I commented out the $recoilDebugStates in recoil's code. Just to be sure that it is not causing the issue.
@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.
The problem still exists (in nightly too). Is there any solution?
If selector caches are an issue, then you can try configuring them, if it is about memory leak due to atoms or selectors no longer being referenced, then that should be addressed with upcoming Recoil garbage collection.
Is there anything here unique to React Native iOS? Curious that it is reported Android has different behavior?
I'm seeing this in all flavors of ReactNative iOS and android. I did see an improvement with using the selector caches for the selector memory leak. However, the other button that creates a new family selector that causes a re-render causes the memory to increase indefinitely (even with the configured caches).
I tried the same experiment with Jotai, and I did not see any memory leak in the same way as with the recoil example described above.
Anyone knows if this still is an 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:
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 normaluseState
, 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
and0.3.1
.