Repository navigation
Description
Description
RCTNetworkTask reads its callback block ivars (_downloadProgressBlock, _completionBlock, _incrementalDataBlock, _responseBlock, _uploadProgressBlock) on the request handler's thread without synchronization, while -invalidate (reached via -cancel) nils the same ivars on a different thread. When the two overlap, the handler thread retains or invokes an already-freed block → EXC_BAD_ACCESS.
This is easy to hit with <Image> and bundled assets that are not .png/.jpg (e.g. .webp, .gif, .jpeg):
RCTIsImageAssetsPathonly treatspng/jpgas bundle assets, soRCTLocalAssetImageLoaderdeclines a bundled.webpand the load falls through toRCTNetworking→RCTFileRequestHandler(anNSOperationQueue).RCTImageLoaderalways setstask.downloadProgressBlockon the task (_loadURLRequest:).RCTImageCacheonly caches results whose response is anNSHTTPURLResponse, so file:// image results are never cached — every mount re-runs the file request.- When the
<Image>unmounts or changessourcemid-load,RCTImageLoader's cancellation block calls[task cancel]on_URLRequestQueue→-invalidate→_downloadProgressBlock = nil(released), concurrently withRCTFileRequestHandler's block calling-URLRequest:didReceiveData:, which reads_downloadProgressBlockandobjc_retains it.
Two related issues widen the window:
RCTFileRequestHandleronly checksisCancelledat the start of its block, so a cancelled task keeps receivingdidReceiveResponse/didReceiveData/didCompleteWithError.- After
-invalidatesets_requestToken = nil,-validateRequestToken:re-adopts whatever token is passed in (if (_requestToken == nil) _requestToken = requestToken;), so those late callbacks are not rejected.
RCTNetworkTask.mm is unchanged on main (only constinit was added to currentRequestId in #59489). Related reports closed without a fix: #32129 (same didReceiveData frame), #54310 (the other side of the same race — objc_release in -invalidate).
Suggested fix (happy to send a PR):
- Early-return from the four
URLRequest:delegate methods when_atomicStatus == RCTNetworkTaskFinished. - Guard the five callback block properties with a mutex (custom accessors) and snapshot them into locals before dispatching.
- In
-invalidate, swap the ivars to locals under the lock and let them release after the lock is dropped.
Steps to reproduce
- Clone https://github.com/sagnik2001/rn-image-networktask-race-repro (template reproducer, RN 0.88.1).
App.tsxremounts 60<Image>s sourced from two bundled.webpfiles on every frame. cd ReproducerApp && yarn install && cd ios && bundle exec pod install- In Xcode: Edit Scheme → Run → Build Configuration: Release (in Debug, assets are served by Metro over HTTP and take a different path). Optional: Diagnostics → Malloc Scribble makes the crash faster.
- Run on a physical device. Relaunch a few times if it doesn't crash within ~3 minutes — it is a race.
- The app crashes with
EXC_BAD_ACCESS.
Results on iPhone 15 (iOS 26.6.2), Release, Malloc Scribble on:
- RN 0.88.1 (this reproducer): 1 of 3 runs crashed (after 4 s); the other two survived 200 s.
- RN 0.86.0 (same screen in our app): 4 of 4 runs crashed, after 108 s, 101 s, 59 s and 13 s.
- Control: the identical screen with the same images converted to
.png(handled byRCTLocalAssetImageLoader, never creates anRCTNetworkTask) ran 2 × 240 s with no crash.
React Native Version
0.88.1 (also 0.86.0)
Affected Platforms
Runtime - iOS
Output of npx @react-native-community/cli info
System:
OS: macOS 26.6.2
CPU: (10) arm64 Apple M4
Memory: 16.00 GB
Binaries:
Node: 22.23.2
Yarn: 1.22.22
npm: 10.9.8
Watchman: 2025.06.30.00
Managers:
CocoaPods: 1.16.2
SDKs:
iOS SDK:
Platforms: DriverKit 25.5, iOS 26.5, macOS 26.5, tvOS 26.5, visionOS 26.5, watchOS 26.5
IDEs:
Xcode: 26.6/17F113
npmPackages:
react-native: 0.86.0 (app), 0.88.1 (reproducer)
iOS:
newArchEnabled: true
Stacktrace or Logs
Production crash (Crashlytics):
Crashed: NSOperationQueue 0x13bef8e00 (QOS: UNSPECIFIED)
0 libobjc.A.dylib objc_retain_x0 + 16
1 libobjc.A.dylib objc_retain + 16
2 MyApp -[RCTNetworkTask URLRequest:didReceiveData:] (RCTNetworkTask.mm:213)
3 MyApp __50-[RCTFileRequestHandler sendRequest:withDelegate:]_block_invoke (RCTFileRequestHandler.mm:92)
4 Foundation <deduplicated_symbol> + 24
5 Foundation -[NSBlockOperation main] + 88
6 Foundation __NSOPERATION_IS_INVOKING_MAIN__ + 16
7 Foundation -[NSOperation start] + 640
8 Foundation __NSOPERATIONQUEUE_IS_STARTING_AN_OPERATION__ + 16
9 Foundation __NSOQSchedule_f + 164
10 libdispatch.dylib _dispatch_block_async_invoke2 + 140
Local reproducer crash (3 of 4 runs; PC is 0 because the dispatched ^{ downloadProgressBlock(length, total); } tail-calls the freed inner block):
Exception Type: EXC_BAD_ACCESS (SIGKILL)
Exception Subtype: KERN_PROTECTION_FAILURE at 0x0000000000000000
ESR: Instruction Abort, Translation fault
Thread 4 Crashed:: Dispatch queue: com.meta.react.turbomodulemanager.queue
0 ??? 0x0
1 libdispatch.dylib _dispatch_call_block_and_release + 32
2 libdispatch.dylib _dispatch_client_callout + 16
3 libdispatch.dylib _dispatch_lane_serial_drain + 740
Thread 10:: Dispatch queue: com.facebook.react.ImageLoaderURLRequestQueue
0 MyApp -[RCTNetworkTask requestToken]
1 MyApp __30-[RCTImageLoader dequeueTasks]_block_invoke
2 libdispatch.dylib _dispatch_call_block_and_release
MANDATORY Reproducer
https://github.com/sagnik2001/rn-image-networktask-race-repro
Screenshots and Videos
No response
Activity
sagnik2001 commentedon Nov 11, 2026
Update: more runs on 0.88.1 and a FastImage control
Same reproducer, same device (iPhone 15, iOS 26.6.2), Release, Malloc Scribble on. Each run was capped at 200 s.
<Image>(bundled.webp)@d11/react-native-fast-image8.13.0 (same.webp, same per-frame remount)So 0.88.1 crashes consistently. The 1-in-3 result in the original report was a small sample.
All four crash reports the device wrote are
EXC_BAD_ACCESSin the same race. The fifth crash, at 8 s, left no report. Three of the four match the trace in the issue (PC0x0oncom.meta.react.turbomodulemanager.queue). The fourth shows the same race from theRCTFileRequestHandlerside, copying an already-freed block:FastImage loads through SDWebImage and never creates an
RCTNetworkTask. Its runs emitted zerotopProgressevents, against up to ~4,700 per<Image>run. Combined with the earlier.pngcontrol (bundled png goes throughRCTLocalAssetImageLoader, no crashes), this points to theRCTFileRequestHandler→RCTNetworkTaskpath as the cause, not webp decoding or the remount pattern.dylancom commentedon Nov 14, 2026
Got the same crashes reported in Sentry with remote images (iOS26 & iOS27).
Having a hard time to reproduce it myself though.