Skip to content

Commit 9ef5107

Browse files
janicduplessisfacebook-github-bot
authored andcommitted
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: #24745 Reviewed By: fkgozali Differential Revision: D15248848 Pulled By: hramos fbshipit-source-id: 1da835cc935dfbf4e7bb6fbf2aea29bfdc9bd6fa
1 parent f2618fd commit 9ef5107

File tree

6 files changed

+132
-2
lines changed

6 files changed

+132
-2
lines changed

Libraries/Blob/BlobManager.js

+28-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const Blob = require('./Blob');
1414
const BlobRegistry = require('./BlobRegistry');
1515
const {BlobModule} = require('../BatchedBridge/NativeModules');
1616

17-
import type {BlobData, BlobOptions} from './BlobTypes';
17+
import type {BlobData, BlobOptions, BlobCollector} from './BlobTypes';
1818

1919
/*eslint-disable no-bitwise */
2020
/*eslint-disable eqeqeq */
@@ -31,6 +31,21 @@ function uuidv4(): string {
3131
});
3232
}
3333

34+
// **Temporary workaround**
35+
// TODO(#24654): Use turbomodules for the Blob module.
36+
// Blob collector is a jsi::HostObject that is used by native to know
37+
// when the a Blob instance is deallocated. This allows to free the
38+
// underlying native resources. This is a hack to workaround the fact
39+
// that the current bridge infra doesn't allow to track js objects
40+
// deallocation. Ideally the whole Blob object should be a jsi::HostObject.
41+
function createBlobCollector(blobId: string): BlobCollector | null {
42+
if (global.__blobCollectorProvider == null) {
43+
return null;
44+
} else {
45+
return global.__blobCollectorProvider(blobId);
46+
}
47+
}
48+
3449
/**
3550
* Module to manage blobs. Wrapper around the native blob module.
3651
*/
@@ -94,7 +109,18 @@ class BlobManager {
94109
*/
95110
static createFromOptions(options: BlobData): Blob {
96111
BlobRegistry.register(options.blobId);
97-
return Object.assign(Object.create(Blob.prototype), {data: options});
112+
return Object.assign(Object.create(Blob.prototype), {
113+
data:
114+
// Reuse the collector instance when creating from an existing blob.
115+
// This will make sure that the underlying resource is only deallocated
116+
// when all blobs that refer to it are deallocated.
117+
options.__collector == null
118+
? {
119+
...options,
120+
__collector: createBlobCollector(options.blobId),
121+
}
122+
: options,
123+
});
98124
}
99125

100126
/**

Libraries/Blob/BlobTypes.js

+3
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@
1010

1111
'use strict';
1212

13+
export opaque type BlobCollector = {};
14+
1315
export type BlobData = {
1416
blobId: string,
1517
offset: number,
1618
size: number,
1719
name?: string,
1820
type?: string,
1921
lastModified?: number,
22+
__collector?: ?BlobCollector,
2023
};
2124

2225
export type BlobOptions = {

Libraries/Blob/RCTBlob.xcodeproj/project.pbxproj

+15
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,15 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
1946172A225F085900E4E008 /* RCTBlobCollector.h in Headers */ = {isa = PBXBuildFile; fileRef = 19461728225F085900E4E008 /* RCTBlobCollector.h */; };
11+
1946172B225F085900E4E008 /* RCTBlobCollector.h in Headers */ = {isa = PBXBuildFile; fileRef = 19461728225F085900E4E008 /* RCTBlobCollector.h */; };
12+
1946172C225F085900E4E008 /* RCTBlobCollector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 19461729225F085900E4E008 /* RCTBlobCollector.mm */; };
13+
1946172D225F085900E4E008 /* RCTBlobCollector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 19461729225F085900E4E008 /* RCTBlobCollector.mm */; };
1014
19BA88FE1F84391700741C5A /* RCTFileReaderModule.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */; };
1115
19BA88FF1F84392900741C5A /* RCTFileReaderModule.h in Headers */ = {isa = PBXBuildFile; fileRef = ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */; };
1216
19BA89001F84392F00741C5A /* RCTFileReaderModule.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */; };
1317
19BA89011F84393D00741C5A /* RCTFileReaderModule.m in Sources */ = {isa = PBXBuildFile; fileRef = ADDFBA6B1F33455F0064C998 /* RCTFileReaderModule.m */; };
18+
19D9CA2622820DA40021BD26 /* RCTBlobCollector.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 19461728225F085900E4E008 /* RCTBlobCollector.h */; };
1419
AD0871131E215B28007D136D /* RCTBlobManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; };
1520
AD0871161E215EC9007D136D /* RCTBlobManager.h in Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; };
1621
AD0871181E215ED1007D136D /* RCTBlobManager.h in Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; };
@@ -28,6 +33,7 @@
2833
dstPath = include/RCTBlob;
2934
dstSubfolderSpec = 16;
3035
files = (
36+
19D9CA2622820DA40021BD26 /* RCTBlobCollector.h in Copy Headers */,
3137
19BA88FE1F84391700741C5A /* RCTFileReaderModule.h in Copy Headers */,
3238
AD08711A1E2162C8007D136D /* RCTBlobManager.h in Copy Headers */,
3339
);
@@ -49,6 +55,8 @@
4955
/* End PBXCopyFilesBuildPhase section */
5056

5157
/* Begin PBXFileReference section */
58+
19461728225F085900E4E008 /* RCTBlobCollector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBlobCollector.h; sourceTree = "<group>"; };
59+
19461729225F085900E4E008 /* RCTBlobCollector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RCTBlobCollector.mm; sourceTree = "<group>"; };
5260
358F4ED71D1E81A9004DF814 /* libRCTBlob.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTBlob.a; sourceTree = BUILT_PRODUCTS_DIR; };
5361
AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTBlobManager.h; sourceTree = "<group>"; };
5462
AD9A43C21DFC7126008DC588 /* RCTBlobManager.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RCTBlobManager.mm; sourceTree = "<group>"; };
@@ -61,6 +69,8 @@
6169
358F4ECE1D1E81A9004DF814 = {
6270
isa = PBXGroup;
6371
children = (
72+
19461728225F085900E4E008 /* RCTBlobCollector.h */,
73+
19461729225F085900E4E008 /* RCTBlobCollector.mm */,
6474
ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */,
6575
ADDFBA6B1F33455F0064C998 /* RCTFileReaderModule.m */,
6676
AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */,
@@ -89,6 +99,7 @@
8999
buildActionMask = 2147483647;
90100
files = (
91101
AD0871161E215EC9007D136D /* RCTBlobManager.h in Headers */,
102+
1946172A225F085900E4E008 /* RCTBlobCollector.h in Headers */,
92103
ADDFBA6C1F33455F0064C998 /* RCTFileReaderModule.h in Headers */,
93104
);
94105
runOnlyForDeploymentPostprocessing = 0;
@@ -98,6 +109,7 @@
98109
buildActionMask = 2147483647;
99110
files = (
100111
19BA88FF1F84392900741C5A /* RCTFileReaderModule.h in Headers */,
112+
1946172B225F085900E4E008 /* RCTBlobCollector.h in Headers */,
101113
AD0871181E215ED1007D136D /* RCTBlobManager.h in Headers */,
102114
);
103115
runOnlyForDeploymentPostprocessing = 0;
@@ -162,6 +174,7 @@
162174
developmentRegion = English;
163175
hasScannedForEncodings = 0;
164176
knownRegions = (
177+
English,
165178
en,
166179
);
167180
mainGroup = 358F4ECE1D1E81A9004DF814;
@@ -180,6 +193,7 @@
180193
isa = PBXSourcesBuildPhase;
181194
buildActionMask = 2147483647;
182195
files = (
196+
1946172C225F085900E4E008 /* RCTBlobCollector.mm in Sources */,
183197
ADDFBA6D1F33455F0064C998 /* RCTFileReaderModule.m in Sources */,
184198
AD9A43C31DFC7126008DC588 /* RCTBlobManager.mm in Sources */,
185199
);
@@ -189,6 +203,7 @@
189203
isa = PBXSourcesBuildPhase;
190204
buildActionMask = 2147483647;
191205
files = (
206+
1946172D225F085900E4E008 /* RCTBlobCollector.mm in Sources */,
192207
19BA89011F84393D00741C5A /* RCTFileReaderModule.m in Sources */,
193208
ADD01A711E09404A00F6D226 /* RCTBlobManager.mm in Sources */,
194209
);

Libraries/Blob/RCTBlobCollector.h

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import <jsi/jsi.h>
9+
10+
using namespace facebook;
11+
12+
@class RCTBlobManager;
13+
14+
namespace facebook {
15+
namespace react {
16+
17+
class JSI_EXPORT RCTBlobCollector : public jsi::HostObject {
18+
public:
19+
RCTBlobCollector(RCTBlobManager *blobManager, const std::string &blobId);
20+
~RCTBlobCollector();
21+
22+
static void install(RCTBlobManager *blobManager);
23+
24+
private:
25+
const std::string blobId_;
26+
RCTBlobManager *blobManager_;
27+
};
28+
29+
} // namespace react
30+
} // namespace facebook

Libraries/Blob/RCTBlobCollector.mm

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import "RCTBlobCollector.h"
9+
10+
#import <React/RCTBridge+Private.h>
11+
#import "RCTBlobManager.h"
12+
13+
namespace facebook {
14+
namespace react {
15+
16+
RCTBlobCollector::RCTBlobCollector(RCTBlobManager *blobManager, const std::string &blobId)
17+
: blobId_(blobId), blobManager_(blobManager) {}
18+
19+
RCTBlobCollector::~RCTBlobCollector() {
20+
RCTBlobManager *blobManager = blobManager_;
21+
NSString *blobId = [NSString stringWithUTF8String:blobId_.c_str()];
22+
dispatch_async([blobManager_ methodQueue], ^{
23+
[blobManager remove:blobId];
24+
});
25+
}
26+
27+
void RCTBlobCollector::install(RCTBlobManager *blobManager) {
28+
__weak RCTCxxBridge *cxxBridge = (RCTCxxBridge *)blobManager.bridge;
29+
[cxxBridge dispatchBlock:^{
30+
if (!cxxBridge || cxxBridge.runtime == nullptr) {
31+
return;
32+
}
33+
jsi::Runtime &runtime = *(jsi::Runtime *)cxxBridge.runtime;
34+
runtime.global().setProperty(
35+
runtime,
36+
"__blobCollectorProvider",
37+
jsi::Function::createFromHostFunction(
38+
runtime,
39+
jsi::PropNameID::forAscii(runtime, "__blobCollectorProvider"),
40+
1,
41+
[blobManager](jsi::Runtime& rt, const jsi::Value& thisVal, const jsi::Value* args, size_t count) {
42+
auto blobId = args[0].asString(rt).utf8(rt);
43+
auto blobCollector = std::make_shared<RCTBlobCollector>(blobManager, blobId);
44+
return jsi::Object::createFromHostObject(rt, blobCollector);
45+
}
46+
)
47+
);
48+
} queue:RCTJSThread];
49+
}
50+
51+
} // namespace react
52+
} // namespace facebook

Libraries/Blob/RCTBlobManager.mm

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#import <React/RCTNetworking.h>
1414
#import <React/RCTUtils.h>
1515
#import <React/RCTWebSocketModule.h>
16+
#import "RCTBlobCollector.h"
1617

1718
static NSString *const kBlobURIScheme = @"blob";
1819

@@ -33,13 +34,16 @@ @implementation RCTBlobManager
3334
RCT_EXPORT_MODULE(BlobModule)
3435

3536
@synthesize bridge = _bridge;
37+
@synthesize methodQueue = _methodQueue;
3638

3739
- (void)setBridge:(RCTBridge *)bridge
3840
{
3941
_bridge = bridge;
4042

4143
std::lock_guard<std::mutex> lock(_blobsMutex);
4244
_blobs = [NSMutableDictionary new];
45+
46+
facebook::react::RCTBlobCollector::install(self);
4347
}
4448

4549
+ (BOOL)requiresMainQueueSetup

0 commit comments

Comments
 (0)