react-native: Severe memory leak affecting fetch(), iOS Release Mode 0.59.0-rc.3, 0.58.6, 0.57.8
š Bug Report
Every call to fetch()
uses memory and does not release it. The larger the fetch response, the more memory leaked.
- Iāve confirmed that this is happening in 0.57.8, 0.58.6, and 0.59.0-rc.3.
- Iāve tried this with both
node-fetch
andwhatwg-fetch
and it happens using both libraries. - It is not isolated to
response.blob()
. It affects all types of responses. - It happens with both Release and Debug.
- It happens with raw
XMLHttpRequest
as well asfetch
. - I can confirm that itās happening on a (real) iPhone X with iOS 12.1.4 and the Simulator for an iPhone 8 with iOS 12.1.
Comparing memory snapshots in Safari found no difference in size even while the Chrome/Xcode memory profiles showed dramatic increases in memory. Iām not great at debugging memory leaks so I could be wrong but I suspect that means it might be on the native side and not on the JS side.
To Reproduce
Iāve created an example repo to reproduce the error in 0.59.0-rc.3: https://github.com/cjroth/react-native-fetch-memory-leak. You can copy the App.js and see that it happens in previous versions as well.
Expected Behavior
After each fetch() call that is no longer referenced, the memory should return to what it was previously.
Code Example
https://github.com/cjroth/react-native-fetch-memory-leak
Environment
React Native Environment Info:
System:
OS: macOS 10.14.1
CPU: (4) x64 Intel(R) Core(TM) i7-4650U CPU @ 1.70GHz
Memory: 16.96 MB / 8.00 GB
Shell: 5.3 - /bin/zsh
Binaries:
Node: 11.10.0 - /usr/local/bin/node
Yarn: 1.13.0 - /usr/local/bin/yarn
npm: 6.8.0 - /usr/local/bin/npm
Watchman: 4.9.0 - /usr/local/bin/watchman
SDKs:
iOS SDK:
Platforms: iOS 12.1, macOS 10.14, tvOS 12.1, watchOS 5.1
IDEs:
Xcode: 10.1/10B61 - /usr/bin/xcodebuild
npmPackages:
react: 16.8.3 => 16.8.3
react-native: 0.59.0-rc.3 => 0.59.0-rc.3
npmGlobalPackages:
react-native-cli: 2.0.1
react-native-git-upgrade: 0.2.7
react-native-rename: 2.4.0
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 13
- Comments: 22 (4 by maintainers)
I think Iāve fixed it. Time to make my first PR into RN.
I found that the RCTNetworkTasks were not de-allocating, and that RCTBlobManager.mm was holding onto itās blobs (NSData) forever after telling the RCTNetworking coordination code about the response.
I was able to reproduce using the iOS simulator and a variant of @cjroth s code with a five mb test file here
This seemed to fix the blob type requests: https://github.com/timsawtell/react-native/commit/236f00a30a6f529f9c69088da3dd6cda1893d4be
However now that Iām trying out just hitting a regular content-type: āapplication/jsonā based response the memory usage issue is still there.
Iām currently debugging that use case (which I think is far more common for apps in general) and Iāll update my fork with anything I find. Since this is my first deep dive into RN networking internals, anyone with some experience please feel free to point out where I should be looking.
what is the most recent release where this did not occur for you?
How can We solve this problem�
RN is supposed to use a forked version of whatwg-fetch that doesnāt use blobs by default to work around this issue. Maybe this is not working anymore for some reason?
OK so this is a bit of a strange one with regards to solution design. The issue as I understand it is that the
RCTBlobManager
isnāt necessarily at fault here as I first thought. I realised that the first attempt at a fix wasnāt right - even though it solved the use case in this issue.The problem is that
whatwg-fetch
isnāt necessarily concerned with what happens to the data from a request after that request is āfinishedā.So
RCTBlobManager
keeps hold of all data from your various fetch operations in your app. It references data via a HashMap with ablobId
as the key. The ābugā is that this data is not removed by the standard fetch APIs. Now thatās notfetch
ās fault, itās notRCTBlobManager
ās fault - it just is šSo I think the approach to resolving this memory consumption issue would be to tell
RCTBlobManager
to release itās data after it receives a āreadā request on that data. By that point it will return the data back over the bridge tofetch
in whatever encoding the programmer has asked for it. I am making an assumption here in saying that once a programmer has read the data in JS then they probably wonāt be referencing that data inRCTBlobManager
again, and it would be safe to release it. The APIs arenāt even there (as far as I can tell) for the programmer to even go get it again via ablobId
- further reason to think this would be the safest approach.PR coming soon after I run some more tests.