Skip to content

Commit c5c79e5

Browse files
janicduplessisfacebook-github-bot
authored andcommitted
Release underlying resources when JS instance in GC'ed on iOS (#24405)
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: #24405 Differential Revision: D15237418 Pulled By: cpojer fbshipit-source-id: 00a94a54b0b172fbc62324364b753d192ac7016a
1 parent bfd0695 commit c5c79e5

File tree

6 files changed

+130
-2
lines changed

6 files changed

+130
-2
lines changed

Libraries/Blob/BlobManager.js

Lines changed: 28 additions & 2 deletions
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('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

Lines changed: 3 additions & 0 deletions
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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
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 */; };
@@ -49,6 +53,8 @@
4953
/* End PBXCopyFilesBuildPhase section */
5054

5155
/* Begin PBXFileReference section */
56+
19461728225F085900E4E008 /* RCTBlobCollector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBlobCollector.h; sourceTree = "<group>"; };
57+
19461729225F085900E4E008 /* RCTBlobCollector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RCTBlobCollector.mm; sourceTree = "<group>"; };
5258
358F4ED71D1E81A9004DF814 /* libRCTBlob.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTBlob.a; sourceTree = BUILT_PRODUCTS_DIR; };
5359
AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTBlobManager.h; sourceTree = "<group>"; };
5460
AD9A43C21DFC7126008DC588 /* RCTBlobManager.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RCTBlobManager.mm; sourceTree = "<group>"; };
@@ -61,6 +67,8 @@
6167
358F4ECE1D1E81A9004DF814 = {
6268
isa = PBXGroup;
6369
children = (
70+
19461728225F085900E4E008 /* RCTBlobCollector.h */,
71+
19461729225F085900E4E008 /* RCTBlobCollector.mm */,
6472
ADDFBA6A1F33455F0064C998 /* RCTFileReaderModule.h */,
6573
ADDFBA6B1F33455F0064C998 /* RCTFileReaderModule.m */,
6674
AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */,
@@ -89,6 +97,7 @@
8997
buildActionMask = 2147483647;
9098
files = (
9199
AD0871161E215EC9007D136D /* RCTBlobManager.h in Headers */,
100+
1946172A225F085900E4E008 /* RCTBlobCollector.h in Headers */,
92101
ADDFBA6C1F33455F0064C998 /* RCTFileReaderModule.h in Headers */,
93102
);
94103
runOnlyForDeploymentPostprocessing = 0;
@@ -98,6 +107,7 @@
98107
buildActionMask = 2147483647;
99108
files = (
100109
19BA88FF1F84392900741C5A /* RCTFileReaderModule.h in Headers */,
110+
1946172B225F085900E4E008 /* RCTBlobCollector.h in Headers */,
101111
AD0871181E215ED1007D136D /* RCTBlobManager.h in Headers */,
102112
);
103113
runOnlyForDeploymentPostprocessing = 0;
@@ -162,6 +172,7 @@
162172
developmentRegion = English;
163173
hasScannedForEncodings = 0;
164174
knownRegions = (
175+
English,
165176
en,
166177
);
167178
mainGroup = 358F4ECE1D1E81A9004DF814;
@@ -180,6 +191,7 @@
180191
isa = PBXSourcesBuildPhase;
181192
buildActionMask = 2147483647;
182193
files = (
194+
1946172C225F085900E4E008 /* RCTBlobCollector.mm in Sources */,
183195
ADDFBA6D1F33455F0064C998 /* RCTFileReaderModule.m in Sources */,
184196
AD9A43C31DFC7126008DC588 /* RCTBlobManager.mm in Sources */,
185197
);
@@ -189,6 +201,7 @@
189201
isa = PBXSourcesBuildPhase;
190202
buildActionMask = 2147483647;
191203
files = (
204+
1946172D225F085900E4E008 /* RCTBlobCollector.mm in Sources */,
192205
19BA89011F84393D00741C5A /* RCTFileReaderModule.m in Sources */,
193206
ADD01A711E09404A00F6D226 /* RCTBlobManager.mm in Sources */,
194207
);

Libraries/Blob/RCTBlobCollector.h

Lines changed: 30 additions & 0 deletions
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

Lines changed: 52 additions & 0 deletions
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

Lines changed: 4 additions & 0 deletions
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)