amplify-js icon indicating copy to clipboard operation
amplify-js copied to clipboard

Subscriptions cause memory leak on ios in RN 0.64.2 when device screen gets locked

Open KristineTrona opened this issue 3 years ago • 9 comments

Before opening, please confirm:

JavaScript Framework

React Native

Amplify APIs

GraphQL API

Amplify Categories

api

Environment information

  System:
    OS: macOS 11.1
    CPU: (4) x64 Intel(R) Core(TM) i5-6267U CPU @ 2.90GHz
    Memory: 563.01 MB / 16.00 GB
    Shell: 5.8 - /bin/zsh
  Binaries:
    Node: 14.16.0 - ~/.nvm/versions/node/v14.16.0/bin/node
    Yarn: 1.22.5 - /usr/local/bin/yarn
    npm: 6.14.11 - ~/.nvm/versions/node/v14.16.0/bin/npm
    Watchman: 4.9.0 - /usr/local/bin/watchman
  Browsers:
    Chrome: 92.0.4515.107
    Firefox: 85.0
    Safari: 14.0.2
  npmPackages:
    @babel/cli: ^7.7.0 => 7.14.8 
    @babel/core: ^7.12.9 => 7.14.8 
    @babel/preset-flow: ^7.0.0 => 7.14.5 
    @babel/runtime: ^7.12.5 => 7.14.8 
    @bugsnag/react-native: ^7.10.1 => 7.11.0 
    @bugsnag/source-maps: ^2.0.0 => 2.2.0 
    @miblanchard/react-native-slider: ^1.1.0 => 1.5.0 
    @react-native-async-storage/async-storage: ^1.15.5 => 1.15.5 
    @react-native-community/datetimepicker: ^3.0.8 => 3.5.2 
    @react-native-community/eslint-config: ^2.0.0 => 2.0.0 
    @react-native-community/masked-view: ^0.1.10 => 0.1.11 
    @react-native-community/netinfo: ^5.3.1 => 5.9.10 
    @react-native-community/push-notification-ios: ^1.8.0 => 1.8.0 
    @react-native-picker/picker: ^1.9.4 => 1.16.4 
    HelloWorld:  0.0.1 
    amazon-cognito-identity-js: ^4.5.11 => 4.6.3 (5.0.6)
    aws-amplify: ^4.2.2 => 4.2.2 
    babel-jest: ^26.6.3 => 26.6.3 
    buffer: ^6.0.3 => 6.0.3 (4.9.2)
    eslint: ^7.14.0 => 7.32.0 
    eslint-config-airbnb: ^18.0.1 => 18.2.1 
    eslint-config-prettier: ^6.7.0 => 6.15.0 
    eslint-config-react-app: ^5.0.2 => 5.2.1 
    eslint-plugin-flowtype: ^4.4.1 => 4.7.0 (2.50.3)
    eslint-plugin-import: ^2.18.2 => 2.23.4 
    eslint-plugin-jsx-a11y: ^6.2.3 => 6.4.1 
    eslint-plugin-prettier: ^3.1.1 => 3.4.0 (3.1.2)
    eslint-plugin-react: ^7.16.0 => 7.24.0 
    example:  0.0.1 
    flow-bin: ^0.107.0 => 0.107.0 
    google-libphonenumber: ^3.2.21 => 3.2.22 
    hermes-inspector-msggen:  1.0.0 
    i18n-js: ^3.5.1 => 3.8.0 
    jest: ^26.6.3 => 26.6.3 
    lodash: ^4.17.20 => 4.17.21 
    metro-react-native-babel-preset: ^0.64.0 => 0.64.0 
    moment: ^2.24.0 => 2.29.1 
    prettier: ^1.19.1 => 1.19.1 (2.3.2)
    prop-types: ^15.7.2 => 15.7.2 
    randomatic: ^3.1.1 => 3.1.1 
    react: 17.0.1 => 17.0.1 
    react-native: 0.64.2 => 0.64.2 
    react-native-cn-richtext-editor: https://github.com/brthrs/react-native-cn-richtext-editor => 2.0.0-rc2 
    react-native-collapsible: ^1.5.3 => 1.6.0 
    react-native-confirmation-code-field: ^6.5.1 => 6.7.0 
    react-native-country-picker-modal: ^2.0.0 => 2.0.0 
    react-native-device-info: ^8.0.6 => 8.1.5 
    react-native-easy-grid: ^0.2.2 => 0.2.2 
    react-native-file-viewer: ^2.1.4 => 2.1.4 
    react-native-fs: ^2.16.6 => 2.18.0 
    react-native-gesture-handler: ^1.5.0 => 1.10.3 
    react-native-image-picker: ^3.2.1 => 3.8.1 
    react-native-keyboard-aware-scroll-view: ^0.9.3 => 0.9.4 
    react-native-maps: ^0.27.1 => 0.27.1 
    react-native-modal: ^11.5.3 => 11.10.0 
    react-native-orientation-locker: ^1.2.0 => 1.3.1 
    react-native-popover-view: ^3.1.1 => 3.1.1 
    react-native-progress: ^4.0.3 => 4.1.2 
    react-native-push-notification: ^7.2.3 => 7.4.0 
    react-native-reanimated: ^1.13.2 => 1.13.3 
    react-native-render-html: ^6.0.0-beta.7 => 6.0.5 
    react-native-safe-area-context: ^3.1.9 => 3.2.0 
    react-native-screens: ^2.17.1 => 2.18.1 
    react-native-scrollable-tab-view: https://github.com/brthrs/react-native-scrollable-tab-view => 1.0.0 
    react-native-size-matters: ^0.4.0 => 0.4.0 
    react-native-splash-screen: ^3.2.0 => 3.2.0 
    react-native-svg: ^12.1.0 => 12.1.1 
    react-native-swiper: ^1.6.0-rc.3 => 1.6.0 
    react-native-table-component: ^1.2.1 => 1.2.1 
    react-native-version-number: ^0.3.6 => 0.3.6 
    react-native-webview: ^8.0.2 => 8.2.1 
    react-native-youtube-iframe: ^1.4.1 => 1.4.2 
    react-navigation: ^4.4.2 => 4.4.4 
    react-navigation-stack: ^2.8.4 => 2.10.4 
    react-navigation-tabs: ^2.9.2 => 2.11.1 
    react-redux: ^7.1.3 => 7.2.4 
    react-test-renderer: 17.0.1 => 17.0.1 
    redux: ^4.0.4 => 4.1.0 
    redux-persist: ^6.0.0 => 6.0.0 
    redux-persist/integration/react:  undefined ()
    redux-saga: ^1.1.3 => 1.1.3 
    redux-saga/effects:  undefined ()
    reselect: ^4.0.0 => 4.0.0 
    rn-secure-storage: ^2.0.7 => 2.0.7 
    styled-components: ^4.4.1 => 4.4.1 
    styled-components/macro:  undefined ()
    styled-components/native:  undefined ()
    styled-components/primitives:  undefined ()
    victory-native: ^35.3.1 => 35.3.3 
  npmGlobalPackages:
    @aws-amplify/cli: 5.1.1
    @bugsnag/source-maps: 2.0.0
    expo-cli: 4.3.2
    nodemon: 2.0.7
    npm: 6.14.11

Describe the bug

Hello,

I use GraphQL with API subscriptions in a RN app and noticed that after upgrading to version 0.64.2 they are causing a memory leak in release builds whenever the device screen gets locked and then unlocked while the app was running in foreground.

After unlocking the device screen the app becomes unresponsive for a few seconds and then crashes. In the error logs I see Out of memory reported. This only happens on iOS, could not reproduce this problem on Android.

I found a temporary workaround by unsubscribing to any updates when appState changes to "inactive" or "background" state and resubscribing, when app comes to foreground, but this is not ideal.

Expected behavior

App does not crash when it comes to foreground after device is unlocked.

Reproduction steps

  1. Create a RN project with any type of real time subscription using API
  2. Create a release build on an iOS device (tested on iPhone mini 12)
  3. Run the app in foreground, wait till the screen gets locked and then unlock it - the app should be unresponsive and crash

Mobile Device

iPhone 12 mini

Mobile Operating System

iOS14.6

KristineTrona avatar Aug 03 '21 09:08 KristineTrona

Hey @KristineTrona 👋 thanks for raising this issue! Can you copy/paste the code where you're establishing the subscription and cleaning it up?

chrisbonifacio avatar Aug 04 '21 21:08 chrisbonifacio

Hey @chrisbonifacio, thank you for the reply! Of course, here is simplified code:

  async componentDidMount() {
    await this.initializeSubscriptions();
  }

  componentWillUnmount() {
    this.removeSubscriptions();
  }

  subscribeToUpdates = async ({ subscription, subscriptionName, input, onSuccess }) => {
    const result = await API.graphql(graphqlOperation(subscription, input)).subscribe({
      next: ({
        value: {
          data: { [subscriptionName]: newData },
          errors,
        },
      }) => {
        if (!newData) {
          return console.log(`Error in subscription data from ${subscriptionName}`, errors);
        }
        return onSuccess(newData);
      },
      error: error => console.log(`Error subscribing to ${subscriptionName} updates`, error),
    });

    return result;
  };

  initializeSubscriptions = async () => {
    const {
      user: { id: userId },
    } = this.props;

    this.onUpdateUser = await this.subscribeToUpdates({
      subscription: onUpdateUser,
      subscriptionName: 'onUpdateUser',
      input: { id: userId },
      onSuccess: data => console.log(data),
    });
  };

  removeSubscriptions = async () => {
    if (this.onUpdateUser) {
      await this.onUpdateUser.unsubscribe();
    }
  };

If I add the following then I no longer have the crash and subscriptions continue to work after app has come to forgeround again:

  async componentDidUpdate(prevProps) {
    const { appState } = this.props;

    if (prevProps.appState === 'active' && appState.match(/inactive|background/)) {
      await this.removeSubscriptions();
    }

    if (prevProps.appState.match(/inactive|background/) && appState === 'active') {
      await this.initializeSubscribers();
    }
  }

KristineTrona avatar Aug 05 '21 10:08 KristineTrona

@chrisbonifacio I've noticed now that even with the appState listener workaround and removing all subscriptions when appstate goes to background, if I lock the app screen and reopen the app a couple of times the error still happens.

This last bit I have only been able to reproduce on iPad so far (testing on iPad mini 2, but also seen it happen on iPad mini4, iPad Air 2, iPad 7). Have not been able to get such a memory leak on an iphone.

KristineTrona avatar Sep 17 '21 07:09 KristineTrona

@KristineTrona , I was unable to reproduce this issue. If possible can you provide more info about your data model?

chintannp avatar Oct 25 '21 20:10 chintannp

I have the same error.

React Native doesn't provider a friendly error message, it just returns Uncaught Error - Unknown.

I managed to get more info on the error using react-native-exception-handler package.

Here's my subscription code.

React.useEffect(() => {

       const categoryCreateSubscription = await
        API.graphql(graphqlOperation(onCreateCategory)).subscribe({
          next: () => {
             console.log('do something...')
          },
        });
        
        return () => {
            categoryCreateSubscription.unsubscribe();

}, []);

Going on background and launching again to foreground crashes the app, even in production.

sydiaplatform avatar Jun 20 '22 17:06 sydiaplatform

I have same issue as sydiaplatform.

thenderson55 avatar Jun 28 '22 13:06 thenderson55

@thenderson55 try and add the error callback, that stopped the app to crash, however, you may want to debug further as to why you are receiving the error. I believe Android does not hold memory for long which could be the cause of the memory leak.

API.graphql(graphqlOperation(onCreateCategory)).subscribe({
          next: () => {
             console.log('do something...')
          },
         error => console.warn(error)
});

sydiaplatform avatar Jul 04 '22 20:07 sydiaplatform

@KristineTrona, @thenderson55 did you ever find a solution for this issue? I am experiencing the same thing.

WilsonWilson avatar Sep 19 '22 23:09 WilsonWilson

@WilsonWilson

The temporary solution from sydiaplatform seems to work. Adding in some error handling.

Edit: Actually it also then creates a new issue. If switch screen on and off breaks again. Can't figure out how to handle the error and resubscribe without breaking.

thenderson55 avatar Sep 21 '22 04:09 thenderson55

Did anybody find a solution to how to handle the error correctly?

thenderson55 avatar Dec 02 '22 05:12 thenderson55

Apologies for the delayed response. This issue was opened while using v4 of the AWS JS Library, and we've since refactored the reconnection logic and are now on v6. We will attempt to reproduce this in v6 but would appreciate if anyone can confirm if upgrading might've resolved for them already.

chrisbonifacio avatar Nov 27 '23 19:11 chrisbonifacio

With the release of the latest major version of Amplify (aws-amplify@>6), we highly recommend updating to the most recent version. Please refer to our release announcement, migration guide, and documentation for more information.

If anyone following this issue upgrades and still experiences the problem, please comment back and we can reopen.

cwomack avatar Dec 18 '23 19:12 cwomack