react-native: Android `fetch` hangs indefinitely with IPv6 hosts on some devices (Happy Eyeballs)

Description

This is a very bizarre issue that has been previously reported a bunch of times, and this is basically a continuation of:

https://github.com/facebook/react-native/issues/29608

I initially started running into this on RN 0.66 with AWS Cognito. Bumping to 0.66.3 didn’t help.

I’m also pretty sure this used to work before and I’m not sure when it broke. It’s on an app that has been shelved for a while.

The problem is very strange because the network request does not seem to be issued, but simply hitting CMD+S to save any file so that a hot-reload is issued will immediately dispatch the network request.

I discovered the promise hanging issue by adding some logs to the fetch calls the cognito library was doing: image Notice how the .then is not executed.

While troubleshooting I came across a mention here of a workaround: https://github.com/facebook/react-native/issues/29608#issuecomment-884521699 (courtesy of @danmaas) which seems to completely resolve the issue.

Here’s the same .then correctly being executed after applying that patch: image

Version

0.66.3

Output of react-native info

System:
    OS: macOS 12.0.1
    CPU: (8) arm64 Apple M1
    Memory: 142.27 MB / 16.00 GB
    Shell: 5.8 - /bin/zsh
  Binaries:
    Node: 16.13.0 - /private/var/folders/9p/k1yqxx0d7rn1nlztg_wm7sbw0000gn/T/xfs-2dcae145/node
    Yarn: 2.4.0-git.20210330.hash-ebcd71d5 - /private/var/folders/9p/k1yqxx0d7rn1nlztg_wm7sbw0000gn/T/xfs-2dcae145/yarn
    npm: 7.20.1 - ~/.nvm/versions/node/v16.13.0/bin/npm
    Watchman: 2021.11.01.00 - /opt/homebrew/bin/watchman
  Managers:
    CocoaPods: 1.11.0 - /Users/andreialecu/.rbenv/shims/pod
  SDKs:
    iOS SDK:
      Platforms: DriverKit 21.0.1, iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0
    Android SDK: Not Found
  IDEs:
    Android Studio: 4.2 AI-202.7660.26.42.7351085
    Xcode: 13.1/13A1030d - /usr/bin/xcodebuild
  Languages:
    Java: 11.0.8 - /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/javac
  npmPackages:
    @react-native-community/cli: Not Found
    react: 17.0.2 => 17.0.2 
    react-native: 0.66.3 => 0.66.3 
    react-native-macos: Not Found
  npmGlobalPackages:
    *react-native*: Not Found

Steps to reproduce

I’m able to reproduce it with this:

  React.useEffect(() => {
    console.log('confirm start', new Date());
    fetch('https://cognito-idp.eu-west-1.amazonaws.com/', {
      method: 'POST',
      mode: 'cors',
    })
      .then(() => console.log('then', new Date()))
      .catch(() => console.log('catch', new Date()));
    setTimeout(() => {
      console.log('5 seconds passed');
    }, 5000);
  }, []);

Output: Screenshot 2021-12-09 at 19 29 06

After applying https://github.com/facebook/react-native/issues/29608#issuecomment-884521699: Screenshot 2021-12-09 at 19 30 32

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

No response

Skip to this comment for the actual cause: https://github.com/facebook/react-native/issues/32730#issuecomment-990764376

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Comments: 48 (17 by maintainers)

Most upvoted comments

Here’s an excellent write-up from Reddit about this issue: https://www.reddit.com/r/RedditEng/comments/v1upr8/ipv6_support_on_android/

As you can see, it can affect things on a large scale.

This really needs to be prioritized by the core react-native team, I can’t imagine it’s not affecting their own apps at Meta.

I’ve built a patched version for use until OkHTTP 5.x is stable (now that since the first of February the implementation of Happy Eyeballs is confirmed). It’s available as marcesengel/react-native#0.66-patched-3.

Add below dependencies to android/app/build.gradle to force use okhttp 5.0.0-alpha.11

  dependencies {
        // others dependencies 
  
        implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.11'
        implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.11'
        implementation 'com.squareup.okhttp3:okhttp-urlconnection:5.0.0-alpha.11'
  }

+1 any feedback on how the 5.0.0-Alpha05 version is working would be great. Or try using it and seeing what you need in terms of observability. Now is a good time to shape the final result.

I see. That makes sense.

Not sure who would need to take a look at this. I can’t review it myself since I’m not familiar with the RN code dealing with this.

Perhaps @yungsters would be able to take a look?

The root cause of this issue seems to be the lack of “Happy Eyeballs” in the underlying okhttp library that react-native is using on Android.

Here’s the issue in okhttp that is tracking this: https://github.com/square/okhttp/issues/506

I think most of the random networking Android slowdowns reported all over the ecosystem are probably related. This is the one I ran into in particular: https://github.com/aws-amplify/amplify-js/issues/5539

Possibly related issues: https://github.com/facebook/react-native/issues/32467 https://github.com/facebook/react-native/issues/29782

This thread also mentions this exact same issue: https://github.com/facebook/react-native/issues/28283#issuecomment-736941578

I believe a PR to implement Happy Eyeballs into okhttp shouldn’t be too hard, if someone would be able to contribute one.

From my understanding a TCP connection to both the IPv6 and IPv4 address needs to be attempted, and the first one to respond is used, while the other one is closed.

This would solve this ubiquitous Android networking problem once and for all.

For me it solved the problem for IPv6 support on android.

dependencies {
      // others dependencies 

      // define a BOM and its version
      implementation(platform("com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.12"))

      // define any required OkHttp artifacts without version
      implementation("com.squareup.okhttp3:okhttp")
      implementation("com.squareup.okhttp3:logging-interceptor")
      implementation("com.squareup.okhttp3:okhttp-urlconnection")
}

Add following dependencies to android/app/build.gradle to force use okhttp 5.0.0-alpha.12

“react”: “18.2.0”, “react-native”: “0.71.11”

If you are using expo and your project is managed, you can not edit android/app/build.gradle yourself. Since I’ve not found any expo plugin, which would make the changes for you, I just made one: expo-android-app-gradle-dependencies

This really needs to be prioritized by the core react-native team

As I already mentioned above, there is nothing to handle here. You can bump your OkHTTP version to 5 alpha and you’ll get the Happy Eyeball support out of the box.

Once 5 becomes stable, we’ll be bumping the default OkHTTP version in the template.

It should be a drop in replacement, it’s backwards compatible down to 3.X. But an extra call to enable

OkHttpClient client = new OkHttpClient.Builder()
    .fastFallback(true)
    .build();

I think I can find some time to create a PR this week. However as of now I really am not aware of how big the API changes are, so I can’t say if I’ll finish it this week.

Does somebody know if switching to major version 5 will affect Fresco?

Do you know if there’s an ETA for the stable release?

The upstream PR was just merged. I believe a stable release is not done yet. According to https://github.com/square/okhttp/issues/6954#issuecomment-1046608631 a new alpha is pending release.

Also, as mentioned here: https://github.com/square/okhttp/issues/506#issuecomment-1046824772

If you’re prepared to deal with small API changes between now and 5.0 final you can put it into production today. For OkHttp ‘alpha’ means ‘API instability’ but the code is extremely stable.

@marcesengel I think this should be addressed in the underlying okhttp library. Perhaps you might want to contribute it there instead?

Essentially this. Please take a look at my answer here: https://github.com/facebook/react-native/pull/33045#issuecomment-1030221142 as I believe that’s the most feasible solution we could take at this stage.

In my case, this issue also affected only some devices. I think it has something to do with the network stack or driver on the device. Or how they interact with IPv6 devices out on the network.