From d1c98dd23c56139bc33b53772028b808618af83e Mon Sep 17 00:00:00 2001 From: "huzhanbo.luc" Date: Tue, 30 Apr 2024 23:10:31 +0800 Subject: [PATCH] fix fetch memory leak --- .../Libraries/Blob/RCTBlobCollector.mm | 6 ++-- .../Libraries/Blob/RCTBlobManager.h | 2 ++ .../Libraries/Blob/RCTBlobManager.mm | 6 ++++ .../react/modules/blob/BlobModule.java | 8 +++++ .../react/reactnativeblob/BlobCollector.cpp | 11 ++++++- .../jni/react/reactnativeblob/BlobCollector.h | 2 ++ .../js/examples/XHR/XHRExampleFetch.js | 32 ++++++++++++++++++- 7 files changed, 63 insertions(+), 4 deletions(-) diff --git a/packages/react-native/Libraries/Blob/RCTBlobCollector.mm b/packages/react-native/Libraries/Blob/RCTBlobCollector.mm index 9b2ca8cb3a0973..60fcc2d1f19117 100644 --- a/packages/react-native/Libraries/Blob/RCTBlobCollector.mm +++ b/packages/react-native/Libraries/Blob/RCTBlobCollector.mm @@ -44,8 +44,10 @@ 1, [blobManager](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) { auto blobId = args[0].asString(rt).utf8(rt); - auto blobCollector = std::make_shared(blobManager, blobId); - return jsi::Object::createFromHostObject(rt, blobCollector); + auto blobCollector = std::make_shared(blobManager, blobId); + auto blobCollectorJsObject = jsi::Object::createFromHostObject(rt, blobCollector); + blobCollectorJsObject.setExternalMemoryPressure(rt, [blobManager lengthOfBlobWithId:[NSString stringWithUTF8String:blobId.c_str()]]); + return blobCollectorJsObject; })); } queue:RCTJSThread]; diff --git a/packages/react-native/Libraries/Blob/RCTBlobManager.h b/packages/react-native/Libraries/Blob/RCTBlobManager.h index bceb8eb5d81f68..38a7238380bb00 100755 --- a/packages/react-native/Libraries/Blob/RCTBlobManager.h +++ b/packages/react-native/Libraries/Blob/RCTBlobManager.h @@ -18,6 +18,8 @@ RCT_EXTERN void RCTEnableBlobManagerProcessingQueue(BOOL enabled); - (void)store:(NSData *)data withId:(NSString *)blobId; +- (NSUInteger)lengthOfBlobWithId:(NSString *)blobId; + - (NSData *)resolve:(NSDictionary *)blob; - (NSData *)resolve:(NSString *)blobId offset:(NSInteger)offset size:(NSInteger)size; diff --git a/packages/react-native/Libraries/Blob/RCTBlobManager.mm b/packages/react-native/Libraries/Blob/RCTBlobManager.mm index 286710d69105cf..9d4cde7c457478 100755 --- a/packages/react-native/Libraries/Blob/RCTBlobManager.mm +++ b/packages/react-native/Libraries/Blob/RCTBlobManager.mm @@ -98,6 +98,12 @@ - (void)store:(NSData *)data withId:(NSString *)blobId _blobs[blobId] = data; } +- (NSUInteger)lengthOfBlobWithId:(NSString *)blobId +{ + std::lock_guard lock(_blobsMutex); + return _blobs[blobId].length; +} + - (NSData *)resolve:(NSDictionary *)blob { NSString *blobId = [RCTConvert NSString:blob[@"blobId"]]; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobModule.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobModule.java index d86962793ed51d..01b37af926888d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobModule.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobModule.java @@ -174,6 +174,14 @@ public void store(byte[] data, String blobId) { } } + @DoNotStrip + public long getLengthOfBlob(String blobId) { + synchronized (mBlobs) { + byte[] data = mBlobs.get(blobId); + return data != null ? data.length : 0; + } + } + @DoNotStrip public void remove(String blobId) { synchronized (mBlobs) { diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/reactnativeblob/BlobCollector.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/reactnativeblob/BlobCollector.cpp index d16ba17aa4c553..5986e67990a197 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/reactnativeblob/BlobCollector.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/reactnativeblob/BlobCollector.cpp @@ -32,6 +32,13 @@ BlobCollector::~BlobCollector() { }); } +size_t BlobCollector::getBlobLength() { + static auto getLengthMethod = jni::findClassStatic(kBlobModuleJavaDescriptor) + ->getMethod("getLengthOfBlob"); + auto length = getLengthMethod(blobModule_, jni::make_jstring(blobId_).get()); + return length; +} + void BlobCollector::nativeInstall( jni::alias_ref, jni::alias_ref blobModule, @@ -53,7 +60,9 @@ void BlobCollector::nativeInstall( auto blobId = args[0].asString(rt).utf8(rt); auto blobCollector = std::make_shared(blobModuleRef, blobId); - return jsi::Object::createFromHostObject(rt, blobCollector); + auto blobCollectorJsObject = jsi::Object::createFromHostObject(rt, blobCollector); + blobCollectorJsObject.setExternalMemoryPressure(rt, blobCollector->getBlobLength()); + return blobCollectorJsObject; })); } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/reactnativeblob/BlobCollector.h b/packages/react-native/ReactAndroid/src/main/jni/react/reactnativeblob/BlobCollector.h index f72e9be5ffb65b..17046b494f2664 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/reactnativeblob/BlobCollector.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/reactnativeblob/BlobCollector.h @@ -18,6 +18,8 @@ class BlobCollector : public jni::HybridClass, BlobCollector(jni::global_ref blobModule, const std::string& blobId); ~BlobCollector(); + size_t getBlobLength(); + static constexpr auto kJavaDescriptor = "Lcom/facebook/react/modules/blob/BlobCollector;"; diff --git a/packages/rn-tester/js/examples/XHR/XHRExampleFetch.js b/packages/rn-tester/js/examples/XHR/XHRExampleFetch.js index 5d386d5ba58c8c..3f49af76b483b9 100644 --- a/packages/rn-tester/js/examples/XHR/XHRExampleFetch.js +++ b/packages/rn-tester/js/examples/XHR/XHRExampleFetch.js @@ -11,7 +11,14 @@ 'use strict'; const React = require('react'); -const {Platform, StyleSheet, Text, TextInput, View} = require('react-native'); +const { + Button, + Platform, + StyleSheet, + Text, + TextInput, + View, +} = require('react-native'); class XHRExampleFetch extends React.Component { responseURL: ?string; @@ -59,6 +66,25 @@ class XHRExampleFetch extends React.Component { return responseHeaders; } + startRepeatedlyFetch() { + const doRequest = () => { + const url = + 'https://microsoftedge.github.io/Demos/json-dummy-data/5MB-min.json'; + fetch(url, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + }) + .then(() => { + console.log('fetch one time'); + }) + .catch(error => console.error(error)); + }; + setInterval(doRequest, 500); + } + render(): React.Node { const responseURL = this.responseURL ? ( @@ -88,6 +114,10 @@ class XHRExampleFetch extends React.Component { return ( +