Uh oh!
There was an error while loading. Please reload this page.
Prevent native blob resource from being de-allocated prematurely - #31392
Prevent native blob resource from being de-allocated prematurely#31392awinograd wants to merge 2 commits into
Conversation
analysis-bot
left a comment
There was a problem hiding this comment.
Code analysis results:
eslintfound some issues. Runyarn lint --fixto automatically fix problems.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Base commit: e509007 |
Base commit: 322a796 |
lachenmayer
commented
Aug 23, 2022
Hi there, we have been facing this issue for quite a while. We are using a resumable/chunked upload client called tus which uses We have been applying this patch (using Without the patch we consistently see crashes with a One of my team members implemented a repro here if anyone is interested: samal-rasmussen/tus-js-client-slice-bug@112843b |
awinograd
commented
Dec 2, 2022
@kelset sorry to bother you with a ping, but would be great to get some 👁️ on this PR. Is there anything I can do to entice a maintainer to take a look? |
Since `blob.slice()` creates a new view onto the same binary data as the original blob, we should re-use the same collector object so that the underlying resource gets deallocated when the last view into the data is released, not the first.
facebook-github-bot
commented
Dec 5, 2022
@cortinico has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
pull-bot
commented
Dec 5, 2022
PR build artifact for d064dfe is ready. |
pull-bot
commented
Dec 5, 2022
PR build artifact for d064dfe is ready. |
This pull request was successfully merged by @awinograd in 36cc71a. When will my fix make it into a release? | Upcoming Releases |
awinograd
commented
Dec 5, 2022
Thank you @kelset@cortinico for pushing this PR forward and getting it merged. Much appreciated! |
) Summary: This PR prevents blob data from being prematurely de-allocated in native code when using slice to create views into an existing blob. Currently, whenever a new blob is created via createFromOptions, BlobManager.js creates a new blobCollector object since options.__collector is never provided. https://github.com/facebook/react-native/blob/dc80b2dcb52fadec6a573a9dd1824393f8c29fdc/Libraries/Blob/BlobManager.js#L115-L123 When the reference to a blobCollector is garbage collected, the corresponding native memory for the blob data is de-allocated. https://github.com/facebook/react-native/blob/27651720b40cab564a0cbd41be56a02584e0c73a/Libraries/Blob/RCTBlobCollector.mm#L19-L25 Since, `blob.slice()` is supposed to create a new view onto the same binary data as the original blob, we need to re-use the same collector object when slicing so that it is not GC'd until the last reference to the binary data is no longer reachable. Currently, since each blob slice gets a new blobCollector object, the memory is de-allocated when the first blob is GC'd. Fixes#29970Fixes#27857 ## Changelog <!-- Help reviewers and the release process by writing your own changelog entry. For an example, see: https://github.com/facebook/react-native/wiki/Changelog --> [iOS] [Fixed] - Blob data is no longer prematurely deallocated when using blob.slice Pull Request resolved: #31392 Test Plan: I could use help coming up with a test plan here. I could add a referential equality check for the blob.data.__collector in `Blob-test` but it doesn't seem quite right to be testing the implementation detail there. Reviewed By: javache Differential Revision: D41730782 Pulled By: cortinico fbshipit-source-id: 5671ae2c69908f4c9acb5d203ba198b41b421294
) Summary: This PR prevents blob data from being prematurely de-allocated in native code when using slice to create views into an existing blob. Currently, whenever a new blob is created via createFromOptions, BlobManager.js creates a new blobCollector object since options.__collector is never provided. https://github.com/facebook/react-native/blob/dc80b2dcb52fadec6a573a9dd1824393f8c29fdc/Libraries/Blob/BlobManager.js#L115-L123 When the reference to a blobCollector is garbage collected, the corresponding native memory for the blob data is de-allocated. https://github.com/facebook/react-native/blob/27651720b40cab564a0cbd41be56a02584e0c73a/Libraries/Blob/RCTBlobCollector.mm#L19-L25 Since, `blob.slice()` is supposed to create a new view onto the same binary data as the original blob, we need to re-use the same collector object when slicing so that it is not GC'd until the last reference to the binary data is no longer reachable. Currently, since each blob slice gets a new blobCollector object, the memory is de-allocated when the first blob is GC'd. Fixes#29970Fixes#27857 ## Changelog <!-- Help reviewers and the release process by writing your own changelog entry. For an example, see: https://github.com/facebook/react-native/wiki/Changelog --> [iOS] [Fixed] - Blob data is no longer prematurely deallocated when using blob.slice Pull Request resolved: #31392 Test Plan: I could use help coming up with a test plan here. I could add a referential equality check for the blob.data.__collector in `Blob-test` but it doesn't seem quite right to be testing the implementation detail there. Reviewed By: javache Differential Revision: D41730782 Pulled By: cortinico fbshipit-source-id: 5671ae2c69908f4c9acb5d203ba198b41b421294
) Summary: This PR prevents blob data from being prematurely de-allocated in native code when using slice to create views into an existing blob. Currently, whenever a new blob is created via createFromOptions, BlobManager.js creates a new blobCollector object since options.__collector is never provided. https://github.com/facebook/react-native/blob/dc80b2dcb52fadec6a573a9dd1824393f8c29fdc/Libraries/Blob/BlobManager.js#L115-L123 When the reference to a blobCollector is garbage collected, the corresponding native memory for the blob data is de-allocated. https://github.com/facebook/react-native/blob/27651720b40cab564a0cbd41be56a02584e0c73a/Libraries/Blob/RCTBlobCollector.mm#L19-L25 Since, `blob.slice()` is supposed to create a new view onto the same binary data as the original blob, we need to re-use the same collector object when slicing so that it is not GC'd until the last reference to the binary data is no longer reachable. Currently, since each blob slice gets a new blobCollector object, the memory is de-allocated when the first blob is GC'd. Fixes#29970Fixes#27857 ## Changelog <!-- Help reviewers and the release process by writing your own changelog entry. For an example, see: https://github.com/facebook/react-native/wiki/Changelog --> [iOS] [Fixed] - Blob data is no longer prematurely deallocated when using blob.slice Pull Request resolved: #31392 Test Plan: I could use help coming up with a test plan here. I could add a referential equality check for the blob.data.__collector in `Blob-test` but it doesn't seem quite right to be testing the implementation detail there. Reviewed By: javache Differential Revision: D41730782 Pulled By: cortinico fbshipit-source-id: 5671ae2c69908f4c9acb5d203ba198b41b421294
Summary: `FileReader.readAsText` / `readAsDataURL` / `readAsArrayBuffer` pass only the plain `blob.data` descriptor to the native module and retain no reference to the `Blob` instance itself. If the caller also drops its reference, the Blob — and the `BlobCollector` attached to `blob.data.__collector` — becomes unreachable while the native read is still in flight. When GC runs in that window, the collector's finalizer unconditionally removes the bytes from the native blob store (`BlobCollector.cpp` calls `BlobModule.remove()` on Android; `RCTBlobCollector.mm` calls `[RCTBlobManager remove:]` on iOS), and the pending read rejects with **"The specified blob is invalid"** (Android) / **"Unable to resolve data for blob"** (iOS). This is not an exotic case: React Native's fetch polyfill (whatwg-fetch) reads blob bodies exactly this way — `readBlobAsText` creates a `FileReader`, calls `reader.readAsText(blob)`, and keeps a reference only to the reader. So a plain `fetch(url).then(r => r.json())`, where the `Response` is not otherwise retained, is subject to this race. This matches the symptom profile of #56884: intermittent failures under many concurrent fetches (GC pressure plus native-module thread-hop latency), affecting both platforms, and disappearing when the same flow is rewritten with `async`/`await` — the suspended frame keeps the `Response` (and therefore the Blob and its collector) reachable, which is exactly the reference this fix restores. The fix retains the Blob on the FileReader instance until the native read settles, completing the reference chain: pending native promise → callbacks → reader → `_blob` → Blob → collector. The reference is cleared when the current read settles (after the existing read-id staleness check, so a read abandoned by `abort()` cannot drop a newer read's reference) and in `abort()` itself, before the abort event is dispatched, so a read started from an abort handler is retained correctly. Memory impact is negligible: the native bytes must live until the read completes anyway — this change only guarantees they do. The root cause is in the shared JS layer, so both Android and iOS are fixed. Fixes#56884 Related prior art: #31392 fixed a different premature-deallocation path in the same subsystem (`blob.slice()` creating a second collector for the same blobId). ## Changelog: [GENERAL] [FIXED] - Retain Blob reference in FileReader during pending native reads to prevent premature deallocation by BlobCollector Pull Request resolved: #57796 Test Plan: - `yarn jest packages/react-native/Libraries/Blob/__tests__/FileReader-test.js` — 19 passed, including 4 new tests: the blob is retained while a read is pending, released on resolve / reject / `abort()`, and a stale read settling after abort does not drop a newer read's blob. - `yarn flow check` — no errors. `eslint` on both changed files — clean. - The GC race itself cannot be reproduced deterministically under Jest (it requires a real engine GC collecting the Blob between dispatch and native execution), so the unit tests assert the reference-retention behavior instead. A deterministic on-device repro is in the issue comment below / #56884. Reviewed By: javache Differential Revision: D114576384 Pulled By: fabriziocucci fbshipit-source-id: ed3f5b51f2d246e041c2b178e3eadd58aeb70038
Summary
This PR prevents blob data from being prematurely de-allocated in native code when using slice to create views into an existing blob. Currently, whenever a new blob is created via createFromOptions, BlobManager.js creates a new blobCollector object since options.__collector is never provided.
https://github.com/facebook/react-native/blob/dc80b2dcb52fadec6a573a9dd1824393f8c29fdc/Libraries/Blob/BlobManager.js#L115-L123
When the reference to a blobCollector is garbage collected, the corresponding native memory for the blob data is de-allocated.
https://github.com/facebook/react-native/blob/27651720b40cab564a0cbd41be56a02584e0c73a/Libraries/Blob/RCTBlobCollector.mm#L19-L25
Since,
blob.slice()is supposed to create a new view onto the same binary data as the original blob, we need to re-use the same collector object when slicing so that it is not GC'd until the last reference to the binary data is no longer reachable. Currently, since each blob slice gets a new blobCollector object, the memory is de-allocated when the first blob is GC'd.Fixes#29970
Fixes#27857
Changelog
[iOS] [Fixed] - Blob data is no longer prematurely deallocated when using blob.slice
Test Plan
I could use help coming up with a test plan here. I could add a referential equality check for the blob.data.__collector in
Blob-testbut it doesn't seem quite right to be testing the implementation detail there.