mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
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
170 lines
4.5 KiB
JavaScript
170 lines
4.5 KiB
JavaScript
/**
|
|
* 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.
|
|
*
|
|
* @flow strict-local
|
|
* @format
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const Blob = require('./Blob');
|
|
const BlobRegistry = require('./BlobRegistry');
|
|
const {BlobModule} = require('../BatchedBridge/NativeModules');
|
|
|
|
import type {BlobData, BlobOptions, BlobCollector} from './BlobTypes';
|
|
|
|
/*eslint-disable no-bitwise */
|
|
/*eslint-disable eqeqeq */
|
|
|
|
/**
|
|
* Based on the rfc4122-compliant solution posted at
|
|
* http://stackoverflow.com/questions/105034
|
|
*/
|
|
function uuidv4(): string {
|
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
|
const r = (Math.random() * 16) | 0,
|
|
v = c == 'x' ? r : (r & 0x3) | 0x8;
|
|
return v.toString(16);
|
|
});
|
|
}
|
|
|
|
// **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.
|
|
*/
|
|
class BlobManager {
|
|
/**
|
|
* If the native blob module is available.
|
|
*/
|
|
static isAvailable = !!BlobModule;
|
|
|
|
/**
|
|
* Create blob from existing array of blobs.
|
|
*/
|
|
static createFromParts(
|
|
parts: Array<Blob | string>,
|
|
options?: BlobOptions,
|
|
): Blob {
|
|
const blobId = uuidv4();
|
|
const items = parts.map(part => {
|
|
if (
|
|
part instanceof ArrayBuffer ||
|
|
(global.ArrayBufferView && part instanceof global.ArrayBufferView)
|
|
) {
|
|
throw new Error(
|
|
"Creating blobs from 'ArrayBuffer' and 'ArrayBufferView' are not supported",
|
|
);
|
|
}
|
|
if (part instanceof Blob) {
|
|
return {
|
|
data: part.data,
|
|
type: 'blob',
|
|
};
|
|
} else {
|
|
return {
|
|
data: String(part),
|
|
type: 'string',
|
|
};
|
|
}
|
|
});
|
|
const size = items.reduce((acc, curr) => {
|
|
if (curr.type === 'string') {
|
|
return acc + global.unescape(encodeURI(curr.data)).length;
|
|
} else {
|
|
return acc + curr.data.size;
|
|
}
|
|
}, 0);
|
|
|
|
BlobModule.createFromParts(items, blobId);
|
|
|
|
return BlobManager.createFromOptions({
|
|
blobId,
|
|
offset: 0,
|
|
size,
|
|
type: options ? options.type : '',
|
|
lastModified: options ? options.lastModified : Date.now(),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create blob instance from blob data from native.
|
|
* Used internally by modules like XHR, WebSocket, etc.
|
|
*/
|
|
static createFromOptions(options: BlobData): Blob {
|
|
BlobRegistry.register(options.blobId);
|
|
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,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Deallocate resources for a blob.
|
|
*/
|
|
static release(blobId: string): void {
|
|
BlobRegistry.unregister(blobId);
|
|
if (BlobRegistry.has(blobId)) {
|
|
return;
|
|
}
|
|
BlobModule.release(blobId);
|
|
}
|
|
|
|
/**
|
|
* Inject the blob content handler in the networking module to support blob
|
|
* requests and responses.
|
|
*/
|
|
static addNetworkingHandler(): void {
|
|
BlobModule.addNetworkingHandler();
|
|
}
|
|
|
|
/**
|
|
* Indicate the websocket should return a blob for incoming binary
|
|
* messages.
|
|
*/
|
|
static addWebSocketHandler(socketId: number): void {
|
|
BlobModule.addWebSocketHandler(socketId);
|
|
}
|
|
|
|
/**
|
|
* Indicate the websocket should no longer return a blob for incoming
|
|
* binary messages.
|
|
*/
|
|
static removeWebSocketHandler(socketId: number): void {
|
|
BlobModule.removeWebSocketHandler(socketId);
|
|
}
|
|
|
|
/**
|
|
* Send a blob message to a websocket.
|
|
*/
|
|
static sendOverSocket(blob: Blob, socketId: number): void {
|
|
BlobModule.sendOverSocket(blob.data, socketId);
|
|
}
|
|
}
|
|
|
|
module.exports = BlobManager;
|