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

Severe memory leak affecting fetch(), iOS Release Mode 0.59.0-rc.3, 0.58.6, 0.57.8 #23801

Closed
cjroth opened this issue Mar 7, 2019 · 22 comments
Closed
Labels
Bug 🌐Networking Related to a networking API. Platform: iOS iOS applications. Resolution: Locked This issue was locked by the bot.

Comments

@cjroth
Copy link

cjroth commented Mar 7, 2019

🐛 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 and whatwg-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 as fetch.
  • 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

screen shot 2019-03-06 at 9 22 40 pm

@react-native-bot react-native-bot added Platform: iOS iOS applications. 🌐Networking Related to a networking API. labels Mar 7, 2019
@zhongwuzw
Copy link
Contributor

@cjroth Hey, did you try Release mode? I remember I tested like #20352 before, it only happened in Debug mode.

@cjroth
Copy link
Author

cjroth commented Mar 7, 2019

@cjroth Hey, did you try Release mode? I remember I tested like #20352 before, it only happened in Debug mode.

@zhongwuzw yes I mentioned that in both the title and the body of the issue. Thank you for replying though!

@jonathangreco
Copy link

@cjroth Hey do you think my issue is also related to this memory leak ?
#23477

When the response is given (and only in IOS) when I ask a permission to store a media into the gallery at almost the same time that the response is handled, my app freeze and need to be restarted.

@cjroth
Copy link
Author

cjroth commented Mar 7, 2019

@jonathangreco no idea, I'd suggest profiling the memory. If it spikes massively before it crashes then it would make sense - if you are downloading something as large as a film through the fetch API and if this memory leak is not just me doing something silly it's likely.

@noahpryor
Copy link

I was able to reproduce using the iOS simulator and a variant of @cjroth s code with a five mb test file
here
screenshot 2019-03-07 21 12 39

@brentvatne
Copy link
Collaborator

what is the most recent release where this did not occur for you?

@cjroth
Copy link
Author

cjroth commented Mar 9, 2019 via email

@timsawtell
Copy link

timsawtell commented Mar 10, 2019

I think I've fixed it. Time to make my first PR into RN.

image

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.

@timsawtell
Copy link

timsawtell commented Mar 10, 2019

This seemed to fix the blob type requests:
timsawtell@236f00a

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.

@timsawtell
Copy link

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 a blobId as the key. The "bug" is that this data is not removed by the standard fetch APIs. Now that's not fetch's fault, it's not RCTBlobManager'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 to fetch 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 in RCTBlobManager 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 a blobId - further reason to think this would be the safest approach.

PR coming soon after I run some more tests.

@cjroth
Copy link
Author

cjroth commented Mar 10, 2019 via email

@timsawtell
Copy link

Do non-blob responses such as JSON also use the blob manager?

Yep. Looks like it. I altered your demo to use my fork of RN, please have a look.
(clone and run)
https://github.com/timsawtell/react-native-fetch-memory-leak/
which uses RN from my fork
timsawtell@fe565e0

@cjroth
Copy link
Author

cjroth commented Mar 11, 2019

@timsawtell seems to be crashing when I run it - here's a screenshot.
Screen Shot 2019-03-10 at 8 27 32 PM

@janicduplessis
Copy link
Contributor

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?

@zhongwuzw
Copy link
Contributor

Seems we never remove blob data from native _blobs dictionary.

@timsawtell Hi, I see your commit, I think it may break the API, could you check my commit? 🤔
zhongwuzw@a1be05e.
And seems it has another leaks besides we save the blob.

@timsawtell
Copy link

Sorry I haven’t replied on this in a while - it looks like either solution (deleting from blob manager on read, or adding a new method to do the same job) brings in implied behavior vs explicit behavior.

(zhongwuzw’s solution is the explicit option).

I’m in favor of the explicit behavior even if it adds a new API binding that’s only going to be used by the iOS implementation. Anyone from the core team have any guidance?

@coocon
Copy link

coocon commented Apr 1, 2019

How can We solve this problem...?

facebook-github-bot pushed a commit that referenced this issue May 8, 2019
Summary:
Our Blob implementation was very problematic because it didn't release its underlying resource when the JS instance was dealocated. The main issue is that the fetch polyfill uses blobs by default if the module is available, which causes large memory leaks.

This fixes it by using the new jsi infra to attach a `jsi::HostObject` (`BlobCollector`)  to `Blob` instances. This way when the `Blob` is collected, the `BlobCollector` also gets collected. Using the `jsi::HostObject` dtor we can schedule the cleanup of native resources. This is definitely not the ideal solution but otherwise it would require rewriting the whole module using TurboModules + jsi.

Fixes #23801, #20352, #21092

[General] [Fixed] - [Blob] Release underlying resources when JS instance in GC'ed
Pull Request resolved: #24745

Reviewed By: fkgozali

Differential Revision: D15248848

Pulled By: hramos

fbshipit-source-id: 1da835cc935dfbf4e7bb6fbf2aea29bfdc9bd6fa
@mtpjr88
Copy link

mtpjr88 commented Oct 23, 2019

My Team recently upgraded to "0.57.2" and have noticed a significant memory leak when making Fetch() requests. Has this been resolved?
@janicduplessis @hramos

@mikecsmith
Copy link

No @mtpjr88 this was still in place after the PR which closed this issue and appears to be a long standing bug. We had to switch to Swift and build natively.

@ambarc
Copy link

ambarc commented Feb 11, 2020

Tried out @zhongwuzw's fix as a patch, and it appears to work on iOS @ RN 0.61.5.

Not sure if this matches fetch's spec - but can confirm some of the functionality.

See before/after with the same user behavior. We repeatedly poll and endpoint which generated a lot of unused (but unleaked) memory.

Memory usage before
image

After
image

@facebook facebook locked as resolved and limited conversation to collaborators May 7, 2020
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label May 7, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Bug 🌐Networking Related to a networking API. Platform: iOS iOS applications. Resolution: Locked This issue was locked by the bot.
Projects
None yet
14 participants