Commit 568eaa6
Fix premature blob deallocation during FileReader reads (#57796)
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: ed3f5b51f2d246e041c2b178e3eadd58aeb700381 parent c2dac6a commit 568eaa6
2 files changed
Lines changed: 132 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
49 | 54 | | |
50 | 55 | | |
51 | 56 | | |
| |||
56 | 61 | | |
57 | 62 | | |
58 | 63 | | |
| 64 | + | |
59 | 65 | | |
60 | 66 | | |
61 | 67 | | |
| |||
110 | 116 | | |
111 | 117 | | |
112 | 118 | | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
113 | 125 | | |
114 | 126 | | |
115 | 127 | | |
116 | 128 | | |
117 | 129 | | |
118 | 130 | | |
| 131 | + | |
119 | 132 | | |
120 | 133 | | |
121 | 134 | | |
| |||
127 | 140 | | |
128 | 141 | | |
129 | 142 | | |
| 143 | + | |
130 | 144 | | |
131 | 145 | | |
132 | 146 | | |
| |||
141 | 155 | | |
142 | 156 | | |
143 | 157 | | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
144 | 161 | | |
145 | 162 | | |
146 | 163 | | |
147 | 164 | | |
148 | 165 | | |
149 | 166 | | |
| 167 | + | |
150 | 168 | | |
151 | 169 | | |
152 | 170 | | |
153 | 171 | | |
154 | 172 | | |
155 | 173 | | |
156 | 174 | | |
| 175 | + | |
157 | 176 | | |
158 | 177 | | |
159 | 178 | | |
| |||
168 | 187 | | |
169 | 188 | | |
170 | 189 | | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
171 | 193 | | |
172 | 194 | | |
173 | 195 | | |
174 | 196 | | |
175 | 197 | | |
176 | 198 | | |
| 199 | + | |
177 | 200 | | |
178 | 201 | | |
179 | 202 | | |
180 | 203 | | |
181 | 204 | | |
182 | 205 | | |
183 | 206 | | |
| 207 | + | |
184 | 208 | | |
185 | 209 | | |
186 | 210 | | |
| |||
192 | 216 | | |
193 | 217 | | |
194 | 218 | | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
195 | 223 | | |
196 | 224 | | |
197 | 225 | | |
| |||
Lines changed: 104 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
275 | 275 | | |
276 | 276 | | |
277 | 277 | | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
278 | 382 | | |
0 commit comments