facebook / react-native

A framework for building native applications using React
https://reactnative.dev
MIT License
118.74k stars 24.29k forks source link

After JS reload, native ios modules crash the app when they try to send events #34105

Open thomasttvo opened 2 years ago

thomasttvo commented 2 years ago

Description

Native ios modules inherit from RCTEventEmitter and use the sendEventWithName method to send events to the JS world.

This method starts out as followed:

- (void)sendEventWithName:(NSString *)eventName body:(id)body
{
      RCTAssert(
            _callableJSModules != nil,
            @"Error when sending event: %@ with body: %@. "
             "RCTCallableJSModules is not set. This is probably because you've "
             "explicitly synthesized the RCTCallableJSModules in %@, even though it's inherited "
             "from RCTEventEmitter.",
            eventName,
            body,
            [self class]);

This means if _callableJSModules is nil, sendEventWithName will crash the app.

It's fine when you start the app the first time, but after a JS reload, I put a timer to output _callableJSModules != nil every second, and it looks like this.

image

I still don't understand the root cause of this issue, but it seems to happen to some native ios modules I've tried this on. I've also tried logging from the parent RCTEventEmitter and can see that some modules do get away with this issue, but I haven't found a way to get the name of those modules.

PS: Even Reanimated has this issue

image

Version

0.68.2

Output of npx react-native info

System:
    OS: macOS 12.4
    CPU: (8) arm64 Apple M1
    Memory: 127.50 MB / 16.00 GB
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 16.15.1 - ~/.nvm/versions/node/v16.15.1/bin/node
    Yarn: 1.22.17 - ~/WebstormProjects/diana/node_modules/.bin/yarn
    npm: 8.11.0 - ~/.nvm/versions/node/v16.15.1/bin/npm
    Watchman: Not Found
  Managers:
    CocoaPods: 1.11.3 - /opt/homebrew/bin/pod
  SDKs:
    iOS SDK:
      Platforms: DriverKit 21.4, iOS 15.5, macOS 12.3, tvOS 15.4, watchOS 8.5
    Android SDK: Not Found
  IDEs:
    Android Studio: 2020.3 AI-203.7717.56.2031.7784292
    Xcode: 13.4.1/13F100 - /usr/bin/xcodebuild
  Languages:
    Java: 11.0.13 - /usr/bin/javac
  npmPackages:
    @react-native-community/cli: Not Found
    react: 17.0.2 => 17.0.2 
    react-native: 0.68.2 => 0.68.2 
    react-native-macos: Not Found
  npmGlobalPackages:
    *react-native*: Not Found

Steps to reproduce

Put this in RCTEventEmitter.m. Watch the output before and after a JS reload


@implementation RCTEventEmitter {
  NSInteger _listenerCount;
  BOOL _observationDisabled;

    @private NSString *__ID;
}

......

- (void)startObserving
{
    dispatch_async(dispatch_get_main_queue(), ^{
        self->__ID = [[NSProcessInfo processInfo] globallyUniqueString];
        [NSTimer scheduledTimerWithTimeInterval:1
                                         target:self
                                       selector:@selector(printCallableJSModules:)
                                       userInfo:nil
                                        repeats:YES];
    });
}

- (void) printCallableJSModules:(NSTimer*)timer
{
    NSLog(@"INIT callableJSModules %@ -- %i", __ID, [self callableJSModules] != nil);
}

Snack, code example, screenshot, or link to a repository

Error you'll get when calling sendEventWithName

image

github-actions[bot] commented 2 years ago
:warning: Missing Environment Information
:information_source: Your issue may be missing information about your development environment. You can obtain the missing information by running react-native info in a console.
github-actions[bot] commented 1 year ago

This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.

github-actions[bot] commented 1 year ago

This issue was closed because it has been stalled for 7 days with no activity.

robinheinze commented 1 year ago

I'm currently experiencing this as well with the LaunchDarkly react-native SDK. I'm working on putting together a minimal repro to demonstrate.

robinheinze commented 1 year ago

I think I have a minimal repro at least for the issue where _callableJSModules is outputting different values every second. I also added a bare minimum custom event emitter, but I'm not able to get it to crash, even after reload, which tracks with what @thomasttvo mentioned where it doesn't seem like this happens in every case :/

https://github.com/robinheinze/EventEmitterJSCallablesBug

I've been experiencing the crashing issue consistently with https://github.com/launchdarkly/react-native-client-sdk, however I don't have a good way to create a runnable repro using that since it depends client account keys. I did open an issue directly there with more info: https://github.com/launchdarkly/react-native-client-sdk/issues/222

github-actions[bot] commented 1 year ago

This issue is waiting for author's feedback since 24 days. Please provide the requested feedback or this will be closed in 7 days.

github-actions[bot] commented 1 year ago

This issue was closed because the author hasn't provided the requested feedback after 7 days.

rickhanlonii commented 1 year ago

I believe this issue is still active, I can confirm I can repro.

gregbrinker commented 11 months ago

Definitely still an issue. What's happening is there are NativeModules that are being subscribed to, but not unsubscribed. For example, here's what something for react-native-sensors would look like:

import { gravity } from "react-native-sensors"

...

useEffect(() => {
  // Subscribe to gravity
  const subscription = gravity.subscribe(
    result => {
      console.log("Gravity: ", result)
    },
    err => {
      console.warn("gravity sensor error", err)
    },
  )

  // Unsubscribe on unmount
  return () => {
    subscription.unsubscribe()
  }
}, [])

During a reload, the subscription.unsubscribe() isn't reached, and there are stale RCTEventEmitters on the native side that are attempting to send events that aren't even being listened to anymore. Each time you reload, there's another emitter that becomes "stale". Here's the output of my NSLog checking if _callableJSModules != nil (and also wrapping sendEventWithName in a check to ensure it's not called when _callableJSModules is nil)

Initially:

1
1
1

After 1st reload:

1
0
1
0
1
0

After 2nd reload:

1
0
0
1
0
0
1
0
0

Larger post about my findings here: https://github.com/facebook/react-native/issues/41188