react-native
react-native copied to clipboard
iOS: App crashes when BlobModule is nil during RCTNetworking initialization
Description
Problem Description
While the exact root cause remains unclear, we are intermittently encountering an NSInvalidArgumentException under the new architecture. The crash appears to originate from RCTAppSetupUtils.mm, where [moduleRegistry moduleForName:@"BlobModule"] unexpectedly returns nil.
Steps to reproduce
While we have not been able to reproduce the issue on our end, it has been consistently reported in Crashlytics. Interestingly, over 50% of the reported crashes are occurring on iPhone 7 Plus and iPhone 6 devices.
React Native Version
0.78.0
Affected Platforms
Runtime - iOS
Areas
Bridgeless - The New Initialization Flow
Output of npx @react-native-community/cli info
System:
OS: macOS 15.1.1
CPU: (10) x64 Apple M2 Pro
Memory: 29.69 MB / 32.00 GB
Shell:
version: "5.9"
path: /bin/zsh
Binaries:
Node:
version: 20.12.0
path: ~/.nvm/versions/node/v20.12.0/bin/node
Yarn:
version: 3.6.4
path: ~/projects/my-project/node_modules/.bin/yarn
npm:
version: 10.8.1
path: ~/.nvm/versions/node/v20.12.0/bin/npm
Watchman:
version: 2024.04.22.00
path: /usr/local/bin/watchman
Managers:
CocoaPods:
version: 1.16.2
path: /Users/me/.rbenv/shims/pod
SDKs:
iOS SDK:
Platforms:
- DriverKit 24.1
- iOS 18.1
- macOS 15.1
- tvOS 18.1
- visionOS 2.1
- watchOS 11.1
Android SDK: Not Found
IDEs:
Android Studio: 2024.3 AI-243.22562.218.2431.13114758
Xcode:
version: 16.1/16B40
path: /usr/bin/xcodebuild
Languages:
Java:
version: 17.0.13
path: /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/bin/javac
Ruby:
version: 3.0.6
path: /Users/me/.rbenv/shims/ruby
npmPackages:
"@react-native-community/cli":
installed: 15.0.1
wanted: 15.0.1
react:
installed: 19.0.0
wanted: 19.0.0
react-native:
installed: 0.78.0
wanted: 0.78.0
react-native-macos: Not Found
npmGlobalPackages:
"*react-native*": Not Found
Android:
hermesEnabled: true
newArchEnabled: true
iOS:
hermesEnabled: true
newArchEnabled: true
Stacktrace or Logs
Fatal Exception: NSInvalidArgumentException
0 CoreFoundation 0x92c60 exceptionPreprocess
1 libobjc.A.dylib 0x14ee4 objc_exception_throw
2 CoreFoundation 0x18d694 -[NSCFString characterAtIndex:].cold.1
3 CoreFoundation 0x18b0b8 -[NSPlaceholderArray initWithCapacity:].cold.1
4 CoreFoundation 0x1fd08 -[NSPlaceholderArray initWithObjects:count:]
5 CoreFoundation 0x4971c +[NSArray arrayWithObjects:count:]
6 React_RCTAppDelegate 0x4f6c RCTAppSetupDefaultModuleFromClass_block_invoke_2 + 103 (RCTAppSetupUtils.mm:103)
7 RCTNetwork 0x68d0 -[RCTNetworking prioritizedHandlers] + 278 (RCTNetworking.mm:278)
8 RCTNetwork 0x6718 -[RCTNetworking handlerForRequest:] + 229 (RCTNetworking.mm:229)
9 RCTNetwork 0x9cc8 -[RCTNetworking networkTaskWithRequest:completionBlock:] + 710 (RCTNetworking.mm:710)
10 RCTNetwork 0x8c8c -[RCTNetworking sendRequest:responseType:incrementalUpdates:responseSender:] + 662 (RCTNetworking.mm:662)
11 RCTNetwork 0xa464 38-[RCTNetworking sendRequest:callback:]block_invoke_2 + 761 (RCTNetworking.mm:761)
Collection element of type 'std::nullptr_t' is not an Objective-C object
Reproducer
sorry I have no Reproducer to provide
Screenshots and Videos
No response
[!TIP] Newer version available: You are on a supported minor version, but it looks like there's a newer patch available - 0.78.1. Please upgrade to the highest patch for your minor or latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If it does not repro, please let us know so we can close out this issue. This helps us ensure we are looking at issues that still exist in the most recent releases.
[!TIP] Newer version available: You are on a supported minor version, but it looks like there's a newer patch available - undefined. Please upgrade to the highest patch for your minor or latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If it does not repro, please let us know so we can close out this issue. This helps us ensure we are looking at issues that still exist in the most recent releases.
[!WARNING] Missing reproducer: We could not detect a reproducible example in your issue report. Please provide either:
- If your bug is UI related: a Snack
- If your bug is build/upgrade related: a project using our Reproducer Template
- Otherwise send us a Pull Request with the RNTesterPlayground.js edited to reproduce your bug.
fyi my app integrated ReactNative with existing Native based app (iOS, Android).
@zhongwuzw Already tried to fix this issue but somehow it doesn't fix the root cause. This is now being looked into by @cipolleschi as per this PR comment.
@hoonjoo-park thanks for the issue. If you can help us explaining when this happens (do you see it when the user reload a screen?) or if you can share the app setup, that would help us pinpoint the actual issue.
@cipolleschi For now, I haven’t been able to determine an exact reproduction scenario. I'm just monitoring this issue by Firebase Crashlytics. ;(
@cipolleschi And what do you mean by "app setup"?
I’ve created a ReactNativeViewController class that handles the React Native iOS setup, following docs here.
Each viewControllers for ReactNative screen simply passes an appKey to this ReactNativeViewController, which then renders the corresponding screen registered with AppRegistry.
In a nutshell:
- I have a
ReactNativeViewControllerthat sets up React Native configs, based on the instructions from the link above. - I have multiple React Native apps registered via
AppRegistry.registerComponent. - These apps are initialized and displayed through their OOReactNativeViewController subclasses.
fyi.
I use reanimated(v3.17.1) and react-native-screens(v4.9.1)
cool, that's what I meant for app setup! you have multiple react native modules that are different screens of your app. From Crashlytics, did you spot a pattern?
- For example users are navigating from a react native surface to another and the app crashes?
- The app crashes always when they navigate to a specific screen?
The assumption here is that, when the first view controller is dismissed, React Native starts the invalidating process. However, the user navigating to a different screen pushes React native to create a new instance while the other has not finished the invalidation. The BlobModule is then still invalidating and the app crashes.
If we can spot some navigation pattern to validate the assumption, we can look for a proper fix.
cc. @okwasniewski who might have other ideas.
@cipolleschi
As you know, I’m currently relying only on Crashlytics reports, so I can’t provide a fully conclusive explanation for the root cause or some patterns.(because, Crashlytics only gives us the crash trace, not the full user flow or non-crashed stack traces)
but I assume that the issue is more likely related to the following below as you mentioned:
“For example users are navigating from a react native surface to another and the app crashes? “
@hoonjoo-park That's correct, but you can add breadcrumbs to your users sessions that will be reported into the crashes, to track what the user was doing: https://firebase.google.com/docs/crashlytics/customize-crash-reports?platform=ios
That's a helpful way to track what's going on in your app when there are crashes that can't be diagnosed otherwise.
Aside from that, I'll try to reproduce the issue locally with some sample code.
I tried to reproduce the issue with a code like this:
func presentDismissAndPresentRN() {
var rnViewcontroller: RNViewController? = RNViewController()
self.present(rnViewcontroller!, animated: true) { [weak self] in
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
self?.dismiss(animated: true)
let rnViewcontroller2: RNViewController? = RNViewController()
rnViewcontroller = nil
self?.present(rnViewcontroller2!, animated: true)
}
}
}
This creates a RNViewController which loads the app, then dismisses it, set the VC to nil, forcing the invalidation.
Then it creates a new RNViewController, forcing the re-creation of the instance.
I also added this delay in the invalidation code in the RCTTurbomoduleManager to make sure that the registry is still invalidating.
- (void)invalidate
{
NSLog(@"invalidate called");
[self _enterInvalidatingState];
+ dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+ dispatch_semaphore_signal(semaphore);
+ });
+ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[self _invalidateModules];
}
My logs are:
// First present
Loading module from class for RCTNetworking
Loading module from class for RCTNetworking done
// Dismiss
invalidate called
// Second present, while the instance is still invalidating
Loading module from class for RCTNetworking
Loading module from class for RCTNetworking done
// Finish the invalidation
invalidate finished execution
you can see in the second execution that the loading is "sandwich"-ed in the invalidation. The blob module is still initialized properly though... although this line is hit.
If i try to present the rn view controller again, sometimes it crashes on a different module.
I think that the right solution here is to have the RCTTurboModuleRegistry to be owned by the single instance and not shared by instances.
@cipolleschi Thank you for the detailed and kind explanation! Just wondering....— does this PR not fully ensures that RCTTurboModuleRegistry is being independently owned? If so, I’ll take a deeper look into the code and possibly open a follow-up PR.
yeah, that PR might fix! The author is a colleague of mine that is working on the problem. Consider that the PR is not landed yet. You can try to apply it to your project.
@cipolleschi @RSNara Really appreciate it! I’ll make a patch and apply it to my project.
Hi @cipolleschi, I’ve applied the patch from this PR and monitored the behavior for about a month, but unfortunately, the same issue is still occurring.
After checking the stack trace logs from firebase, it doesn’t seem like the user is rapidly and frequently opening and closing the React Native screen(module). It appears to crash when closing a specific RN module — the crash usually occurs within about 1 second after the exit event is triggered.
I’ll take a closer look at that particular RN module to see if there’s anything unusual, and I’ll follow up once I find something.
Hi @cipolleschi, Sorry for the late follow-up!
After digging deeper into the issue, it seems the crash isn’t related with "dismissing a specific screen". It made me wonder if the real issue might be.... new ReactNativeDelegate gets initialized every time when ReactNativeViewController is created.
I followed this guide when structuring my app’s RN integration, but it doesn’t seem to account for apps like mine.(i.e., multiple registered RN components).
So... I’m now considering making the ReactNativeDelegate a singleton. Since all RN components are going to be bundled into a single JS file anyway, I think reusing the same delegate and its factory across screens could be a safe and efficient approach in this case.
Would love to hear your thoughts on this idea.
@hoonjoo-park I think your idea is solid. The guide was thought for apps that have to integrate react native in one single screen. So when the screen is dismissed, it's ok to destroy the ReactNativeDelegate.
If your app has multiple screens and you want to reuse the same delegate, that's definitely a good solution. I would probably not use a singleton, but I believe that you have some sort of coordinator that pushes the various VC. I would rather have the coordinator be the ReactNativeDelegate or create and hold a refernece to the delegate, and then pass it around. This should made the code more testable and easier to maintain. But that's a design decision on your side! 😉