react-native-device-info: Parallel calls to getUserAgent() cause a runtime exception

Bug

Hello. I’ve used this package for years and I really like it. Thanks for your hard work!

Today I updated to the 3.0.0 version. I noticed some odd behavior, and have narrowed it down to the fact that parallel calls to the DeviceInfo.getUserAgent() function cause one of them to throw an exception.

I think I’m to a point where I’m no longer calling duplicate functions at the same time with the help of async await, so I’ve worked around the issue … here’s my end-result commit of going from 2.3.2 to 3.0.0 for reference : https://github.com/OurVoiceUSA/HelloVoter/commit/4cb41e3538f73932cfec411b7f03bd1e42bbb42e

Environment info

React native info output:

System:
    OS: macOS 10.14.6
    CPU: (12) x64 Intel(R) Core(TM) i7-8700B CPU @ 3.20GHz
    Memory: 374.36 MB / 32.00 GB
    Shell: 3.2.57 - /bin/bash
  Binaries:
    Node: 10.16.0 - /usr/local/bin/node
    npm: 6.11.3 - /usr/local/bin/npm
  SDKs:
    iOS SDK:
      Platforms: iOS 12.4, macOS 10.14, tvOS 12.4, watchOS 5.3
    Android SDK:
      API Levels: 27, 28, 29
      Build Tools: 28.0.3, 29.0.1, 29.0.2
      System Images: android-28 | Google Play Intel x86 Atom, android-29 | Google Play Intel x86 Atom
  IDEs:
    Android Studio: 3.5 AI-191.8026.42.35.5791312
    Xcode: 10.3/10G8 - /usr/bin/xcodebuild
  npmPackages:
    react: ^16.9.0 => 16.9.0 
    react-native: ^0.60.5 => 0.60.5 
  npmGlobalPackages:
    react-native-cli: 2.0.1

Library version: 3.0.0

Steps To Reproduce

Call the same function twice before the promise resolves, like this:

  componentDidMount() {
    DeviceInfo.getUserAgent().then(i => this.setState({UserAgent: i})).catch(e => console.warn(e));
    DeviceInfo.getUserAgent().then(i => this.setState({UserAgent: i})).catch(e => console.warn(e));
  }

Describe what you expected to happen:

No exception should be thrown.

Reproducible sample code

You can init a new project like so

react-native init AwesomeProject
cd AwesomeProject
npm install react-native-device-info
react-native link react-native-device-info
(cd ios && pod install)

Have App.js be the following:

import React, {PureComponent} from 'react';
import {
  View,
  Text,
} from 'react-native';
import DeviceInfo from 'react-native-device-info';

export default class App extends PureComponent {

  constructor(props) {
    super(props);
    this.state = {
      UserAgent: "unknown",
    };
  }

  componentDidMount() {
    DeviceInfo.getUserAgent().then(i => this.setState({UserAgent: i})).catch(e => console.warn(e));
    DeviceInfo.getUserAgent().then(i => this.setState({UserAgent: i})).catch(e => console.warn(e));
  }

  render() {
    const { UserAgent } = this.state;
    return (
      <View style={{flex: 1, margin: 50}}>
        <Text>Hello DeviceInfo! UserAgent => {UserAgent}</Text>
      </View>
    );
  }
};

And finally react-native run-ios and you’ll see the warning from the caught exception. In this example it’s complaining about WKErrorDomain, and at some point the message said something about thread safety, and I didn’t capture the message in detail.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 5
  • Comments: 18

Most upvoted comments

it was more than a second in some situations (!)

“during main queue setup” - would that add to app startup time? This method was by far the slowest method in the library and kicked off much of the async/sync/constants ruckus

Regardless, the problem is - if I understand correctly - two calls going in to the native code at the same time and racing somehow. Some sort of synchronization / mutex to stop that from happening and cache the result seems like a reasonable way to repair it

None sorry - non open source work has been hopping for me the last few months so my queue is pretty deep at the moment, no one has proposed a PR, and it’s (to me) a rare corner case based on interest here (small, though I understand it’s important to the interested parties) vs user base (massive). I’m open to solutions but won’t have time do anything myself for a while

You are right, I totally overlooked the fact that there’s also a webview involved! So although the initial RN call doesn’t run in parallel, it may still run like that in the web view.

Just trying to learn a bit more about iOS, not an expert neither, and it this seemed interesting.

just a note to say I can definitely reproduce this - looking at ways to synchronize etc so that it doesn’t matter how many times it’s called at once