Release underlying resources when JS instance in GC'ed (#24745)

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: https://github.com/facebook/react-native/pull/24745

Reviewed By: fkgozali

Differential Revision: D15248848

Pulled By: hramos

fbshipit-source-id: 1da835cc935dfbf4e7bb6fbf2aea29bfdc9bd6fa
This commit is contained in:
Janic Duplessis 2019-05-08 14:10:14 -07:00 коммит произвёл Facebook Github Bot
Родитель f2618fd81b
Коммит 9ef5107d04
6 изменённых файлов: 132 добавлений и 2 удалений

Просмотреть файл

@ -14,7 +14,7 @@ const Blob = require('./Blob');
const BlobRegistry = require('./BlobRegistry'); const BlobRegistry = require('./BlobRegistry');
const {BlobModule} = require('../BatchedBridge/NativeModules'); const {BlobModule} = require('../BatchedBridge/NativeModules');
import type {BlobData, BlobOptions} from './BlobTypes'; import type {BlobData, BlobOptions, BlobCollector} from './BlobTypes';
/*eslint-disable no-bitwise */ /*eslint-disable no-bitwise */
/*eslint-disable eqeqeq */ /*eslint-disable eqeqeq */
@ -31,6 +31,21 @@ function uuidv4(): string {
}); });
} }
// **Temporary workaround**
// TODO(#24654): Use turbomodules for the Blob module.
// Blob collector is a jsi::HostObject that is used by native to know
// when the a Blob instance is deallocated. This allows to free the
// underlying native resources. This is a hack to workaround the fact
// that the current bridge infra doesn't allow to track js objects
// deallocation. Ideally the whole Blob object should be a jsi::HostObject.
function createBlobCollector(blobId: string): BlobCollector | null {
if (global.__blobCollectorProvider == null) {
return null;
} else {
return global.__blobCollectorProvider(blobId);
}
}
/** /**
* Module to manage blobs. Wrapper around the native blob module. * Module to manage blobs. Wrapper around the native blob module.
*/ */
@ -94,7 +109,18 @@ class BlobManager {
*/ */
static createFromOptions(options: BlobData): Blob { static createFromOptions(options: BlobData): Blob {
BlobRegistry.register(options.blobId); BlobRegistry.register(options.blobId);
return Object.assign(Object.create(Blob.prototype), {data: options}); return Object.assign(Object.create(Blob.prototype), {
data:
// Reuse the collector instance when creating from an existing blob.
// This will make sure that the underlying resource is only deallocated
// when all blobs that refer to it are deallocated.
options.__collector == null
? {
...options,
__collector: createBlobCollector(options.blobId),
}
: options,
});
} }
/** /**

Просмотреть файл

@ -10,6 +10,8 @@
'use strict'; 'use strict';
export opaque type BlobCollector = {};
export type BlobData = { export type BlobData = {
blobId: string, blobId: string,
offset: number, offset: number,
@ -17,6 +19,7 @@ export type BlobData = {
name?: string, name?: string,
type?: string, type?: string,
lastModified?: number, lastModified?: number,
__collector?: ?BlobCollector,
}; };
export type BlobOptions = { export type BlobOptions = {

Просмотреть файл

@ -7,10 +7,15 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
1946172A225F085900E4E008 /* RCTBlobCollector.h in Headers */ = {isa = PBXBuildFile; fileRef = 19461728225F085900E4E008 /* RCTBlobCollector.h */; };
1946172B225F085900E4E008 /* RCTBlobCollector.h in Headers */ = {isa = PBXBuildFile; fileRef = 19461728225F085900E4E008 /* RCTBlobCollector.h */; };
1946172C225F085900E4E008 /* RCTBlobCollector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 19461729225F085900E4E008 /* RCTBlobCollector.mm */; };
1946172D225F085900E4E008 /* RCTBlobCollector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 19461729225F085900E4E008 /* RCTBlobCollector.mm */; };
19BA88FE1F84391700741C5A /* RCTFileReaderModule.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */; }; 19BA88FE1F84391700741C5A /* RCTFileReaderModule.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */; };
19BA88FF1F84392900741C5A /* RCTFileReaderModule.h in Headers */ = {isa = PBXBuildFile; fileRef = ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */; }; 19BA88FF1F84392900741C5A /* RCTFileReaderModule.h in Headers */ = {isa = PBXBuildFile; fileRef = ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */; };
19BA89001F84392F00741C5A /* RCTFileReaderModule.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */; }; 19BA89001F84392F00741C5A /* RCTFileReaderModule.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */; };
19BA89011F84393D00741C5A /* RCTFileReaderModule.m in Sources */ = {isa = PBXBuildFile; fileRef = ADDFBA6B1F33455F0064C998 /* RCTFileReaderModule.m */; }; 19BA89011F84393D00741C5A /* RCTFileReaderModule.m in Sources */ = {isa = PBXBuildFile; fileRef = ADDFBA6B1F33455F0064C998 /* RCTFileReaderModule.m */; };
19D9CA2622820DA40021BD26 /* RCTBlobCollector.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 19461728225F085900E4E008 /* RCTBlobCollector.h */; };
AD0871131E215B28007D136D /* RCTBlobManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; }; AD0871131E215B28007D136D /* RCTBlobManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; };
AD0871161E215EC9007D136D /* RCTBlobManager.h in Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; }; AD0871161E215EC9007D136D /* RCTBlobManager.h in Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; };
AD0871181E215ED1007D136D /* RCTBlobManager.h in Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; }; AD0871181E215ED1007D136D /* RCTBlobManager.h in Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; };
@ -28,6 +33,7 @@
dstPath = include/RCTBlob; dstPath = include/RCTBlob;
dstSubfolderSpec = 16; dstSubfolderSpec = 16;
files = ( files = (
19D9CA2622820DA40021BD26 /* RCTBlobCollector.h in Copy Headers */,
19BA88FE1F84391700741C5A /* RCTFileReaderModule.h in Copy Headers */, 19BA88FE1F84391700741C5A /* RCTFileReaderModule.h in Copy Headers */,
AD08711A1E2162C8007D136D /* RCTBlobManager.h in Copy Headers */, AD08711A1E2162C8007D136D /* RCTBlobManager.h in Copy Headers */,
); );
@ -49,6 +55,8 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
19461728225F085900E4E008 /* RCTBlobCollector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBlobCollector.h; sourceTree = "<group>"; };
19461729225F085900E4E008 /* RCTBlobCollector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RCTBlobCollector.mm; sourceTree = "<group>"; };
358F4ED71D1E81A9004DF814 /* libRCTBlob.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTBlob.a; sourceTree = BUILT_PRODUCTS_DIR; }; 358F4ED71D1E81A9004DF814 /* libRCTBlob.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTBlob.a; sourceTree = BUILT_PRODUCTS_DIR; };
AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTBlobManager.h; sourceTree = "<group>"; }; AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTBlobManager.h; sourceTree = "<group>"; };
AD9A43C21DFC7126008DC588 /* RCTBlobManager.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RCTBlobManager.mm; sourceTree = "<group>"; }; AD9A43C21DFC7126008DC588 /* RCTBlobManager.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RCTBlobManager.mm; sourceTree = "<group>"; };
@ -61,6 +69,8 @@
358F4ECE1D1E81A9004DF814 = { 358F4ECE1D1E81A9004DF814 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
19461728225F085900E4E008 /* RCTBlobCollector.h */,
19461729225F085900E4E008 /* RCTBlobCollector.mm */,
ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */, ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */,
ADDFBA6B1F33455F0064C998 /* RCTFileReaderModule.m */, ADDFBA6B1F33455F0064C998 /* RCTFileReaderModule.m */,
AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */, AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */,
@ -89,6 +99,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
AD0871161E215EC9007D136D /* RCTBlobManager.h in Headers */, AD0871161E215EC9007D136D /* RCTBlobManager.h in Headers */,
1946172A225F085900E4E008 /* RCTBlobCollector.h in Headers */,
ADDFBA6C1F33455F0064C998 /* RCTFileReaderModule.h in Headers */, ADDFBA6C1F33455F0064C998 /* RCTFileReaderModule.h in Headers */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -98,6 +109,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
19BA88FF1F84392900741C5A /* RCTFileReaderModule.h in Headers */, 19BA88FF1F84392900741C5A /* RCTFileReaderModule.h in Headers */,
1946172B225F085900E4E008 /* RCTBlobCollector.h in Headers */,
AD0871181E215ED1007D136D /* RCTBlobManager.h in Headers */, AD0871181E215ED1007D136D /* RCTBlobManager.h in Headers */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -162,6 +174,7 @@
developmentRegion = English; developmentRegion = English;
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
knownRegions = ( knownRegions = (
English,
en, en,
); );
mainGroup = 358F4ECE1D1E81A9004DF814; mainGroup = 358F4ECE1D1E81A9004DF814;
@ -180,6 +193,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
1946172C225F085900E4E008 /* RCTBlobCollector.mm in Sources */,
ADDFBA6D1F33455F0064C998 /* RCTFileReaderModule.m in Sources */, ADDFBA6D1F33455F0064C998 /* RCTFileReaderModule.m in Sources */,
AD9A43C31DFC7126008DC588 /* RCTBlobManager.mm in Sources */, AD9A43C31DFC7126008DC588 /* RCTBlobManager.mm in Sources */,
); );
@ -189,6 +203,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
1946172D225F085900E4E008 /* RCTBlobCollector.mm in Sources */,
19BA89011F84393D00741C5A /* RCTFileReaderModule.m in Sources */, 19BA89011F84393D00741C5A /* RCTFileReaderModule.m in Sources */,
ADD01A711E09404A00F6D226 /* RCTBlobManager.mm in Sources */, ADD01A711E09404A00F6D226 /* RCTBlobManager.mm in Sources */,
); );

Просмотреть файл

@ -0,0 +1,30 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <jsi/jsi.h>
using namespace facebook;
@class RCTBlobManager;
namespace facebook {
namespace react {
class JSI_EXPORT RCTBlobCollector : public jsi::HostObject {
public:
RCTBlobCollector(RCTBlobManager *blobManager, const std::string &blobId);
~RCTBlobCollector();
static void install(RCTBlobManager *blobManager);
private:
const std::string blobId_;
RCTBlobManager *blobManager_;
};
} // namespace react
} // namespace facebook

Просмотреть файл

@ -0,0 +1,52 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTBlobCollector.h"
#import <React/RCTBridge+Private.h>
#import "RCTBlobManager.h"
namespace facebook {
namespace react {
RCTBlobCollector::RCTBlobCollector(RCTBlobManager *blobManager, const std::string &blobId)
: blobId_(blobId), blobManager_(blobManager) {}
RCTBlobCollector::~RCTBlobCollector() {
RCTBlobManager *blobManager = blobManager_;
NSString *blobId = [NSString stringWithUTF8String:blobId_.c_str()];
dispatch_async([blobManager_ methodQueue], ^{
[blobManager remove:blobId];
});
}
void RCTBlobCollector::install(RCTBlobManager *blobManager) {
__weak RCTCxxBridge *cxxBridge = (RCTCxxBridge *)blobManager.bridge;
[cxxBridge dispatchBlock:^{
if (!cxxBridge || cxxBridge.runtime == nullptr) {
return;
}
jsi::Runtime &runtime = *(jsi::Runtime *)cxxBridge.runtime;
runtime.global().setProperty(
runtime,
"__blobCollectorProvider",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "__blobCollectorProvider"),
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<RCTBlobCollector>(blobManager, blobId);
return jsi::Object::createFromHostObject(rt, blobCollector);
}
)
);
} queue:RCTJSThread];
}
} // namespace react
} // namespace facebook

Просмотреть файл

@ -13,6 +13,7 @@
#import <React/RCTNetworking.h> #import <React/RCTNetworking.h>
#import <React/RCTUtils.h> #import <React/RCTUtils.h>
#import <React/RCTWebSocketModule.h> #import <React/RCTWebSocketModule.h>
#import "RCTBlobCollector.h"
static NSString *const kBlobURIScheme = @"blob"; static NSString *const kBlobURIScheme = @"blob";
@ -33,6 +34,7 @@ static NSString *const kBlobURIScheme = @"blob";
RCT_EXPORT_MODULE(BlobModule) RCT_EXPORT_MODULE(BlobModule)
@synthesize bridge = _bridge; @synthesize bridge = _bridge;
@synthesize methodQueue = _methodQueue;
- (void)setBridge:(RCTBridge *)bridge - (void)setBridge:(RCTBridge *)bridge
{ {
@ -40,6 +42,8 @@ RCT_EXPORT_MODULE(BlobModule)
std::lock_guard<std::mutex> lock(_blobsMutex); std::lock_guard<std::mutex> lock(_blobsMutex);
_blobs = [NSMutableDictionary new]; _blobs = [NSMutableDictionary new];
facebook::react::RCTBlobCollector::install(self);
} }
+ (BOOL)requiresMainQueueSetup + (BOOL)requiresMainQueueSetup