Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fetch API breaks when turning network off and on. #19709

Closed
5 tasks done
roberthoenig opened this issue Jun 14, 2018 · 20 comments
Closed
5 tasks done

Fetch API breaks when turning network off and on. #19709

roberthoenig opened this issue Jun 14, 2018 · 20 comments
Labels
Bug 🌐Networking Related to a networking API. Platform: Linux Building on Linux. Stale There has been a lack of activity on this issue and it may be closed soon.

Comments

@roberthoenig
Copy link
Contributor

Environment

Environment:
OS: Linux 4.15
Node: 8.11.1
Yarn: 1.5.1
npm: 5.6.0
Watchman: Not Found
Xcode: N/A
Android Studio: 3.1 AI-173.4720617

Packages: (wanted => installed)
react: 16.3.1 => 16.3.1
react-native: 0.55.4 => 0.55.0

Note: This is the environment where we built RN v0.55.0 from source to reproduce the bug. Originally, we experienced the issue with RN v0.55.4 zulip/zulip-mobile#2287

Description

On Android, calls to fetch() take several minutes after turning the internet connection off and on again.
An initial bug report can be found here: zulip/zulip-mobile#2287. The same report features a detailed comment on how to reproduce the bug in the app it was reported for.

Steps to Reproduce

The issue can be reproduced on Android with the following app:

export default class App extends Component<Props> {

  myFunction() {
    console.log("Button pressed");
    NetInfo.getConnectionInfo().then((connectionInfo) => {
      console.log('Initial, type: ' + connectionInfo.type + ', effectiveType: ' + connectionInfo.effectiveType);
    });
    fetch('https://facebook.github.io/react-native/movies.json')
    .then((response) => console.log("response", response))
    .catch((error) => {
      console.error(error);
    });
  };

  render() {
    return (
      <View style={styles.container}>
        <Button
          onPress={this.myFunction}
          title="Learn More"
          color="#841584"
          accessibilityLabel="Learn more about this purple button"
        />
      </View>
    );
  }
}

I then ran the app on an emulator, clicked the button a couple times and disabled and enabled the network with

$ adb shell svc data disable
$ adb shell svc data enable

Here is the app's output in Chrome Dev Tools:

Button pressed
12:25:45.945 App.js:29 Initial, type: cellular, effectiveType: 4g
12:25:46.222 App.js:32 response Response {type: "default", status: 200, ok: true, statusText: undefined, headers: Headers, …}
12:25:53.044 App.js:27 Button pressed
12:25:53.053 App.js:29 Initial, type: none, effectiveType: unknown
12:25:58.261 App.js:27 Button pressed
12:25:58.267 App.js:29 Initial, type: cellular, effectiveType: 4g
12:35:46.703 App.js:32 response Response {type: "default", status: 200, ok: true, statusText: undefined, headers: Headers, …}
12:35:46.741 App.js:32 response Response {type: "default", status: 200, ok: true, statusText: undefined, headers: Headers, …}

Two things are interesting about the output above.

  • After turning the network off and on and pressing the button, fetch does not receive the resource.
  • After waiting for ~10 minutes, two responses come in. One is probably for the request sent out while
    the app was offline, and the other for the request sent out after the app was brought back online.

I also wrote a little app in Android Studio that uses two buttons and OkHttp to reproduce the issue. Reproduction steps can be found in the repo's README.md. https://github.com/roberthoenig/react-native-fetch-bug

Expected Behavior

I expect fetch() to work the same before and after turning off and on the internet connection.
In particular, I expect a prompt response to requests I send out.

Actual Behavior

After turning the internet connection off and on, fetch() did not respond promptly. It took ~10 minutes.
In other trials, this varied from ~ 2 - 15 minutes. After investigating RN's source code, I stumbled upon this
line:

I then added

client.connectionPool().evictAll();

before it.

After adding this line, dis- and reconnecting didn't confuse RN anymore. After reconnecting, requests just work. Oc, this is not a final solution, since client.connectionPool().evictAll(); clears all previous network connections made by this client.

A possible grand unifying theory of what is going on under the hood for this bug:

  1. I disconnect from the network.
  2. While offline, some part of the app that we have no influence on makes some network request X.
  3. X doesn't get sent out to the internet, because we're offline. However, the default timeout for X we have no control over is set to 0, meaning "no timeout".
  4. I reconnect to the network.
  5. Because X never got sent out, RN will never receive a response for X. However, the timeout is set to 0, so RN will wait for X forever. Reconnecting to the network won't change anything for X.
  6. I make my own request Y. Y gets "enqueued" by the same client that enqueued X, meaning that they'll share the same connectionPool.
  7. X needs to be processed before Y can get processed. X blocks Y. This might be our bug.
  8. At some point in time some random event clears super old connection or something like that. X gets removed. Y gets finally dispatched, but super late.
@react-native-bot react-native-bot added 🌐Networking Related to a networking API. Platform: Linux Building on Linux. labels Jun 14, 2018
roberthoenig added a commit to roberthoenig/react-native that referenced this issue Jun 21, 2018
…ebook#19709).

This bug is probably actually a bug in OkHttp: square/okhttp#4079
Both issues linked above contain extensive details about the issue, its likely origins and
how to reproduce it. A short summary of the issue and the fix in this commit:

On Android, disconnecting from the network somehow corrupts the idle connections in okhttp
clients. New requests made over these clients fail. This commit works around that bug by
clearing the idle connection pool when Android disconnects from the network.
roberthoenig added a commit to roberthoenig/react-native that referenced this issue Jun 22, 2018
…book#19709.

This bug is probably actually a bug in OkHttp: square/okhttp#4079
Both issues linked above contain extensive details about the issue, its likely origins and
how to reproduce it. A short summary of the issue and the fix in this commit:

On Android, disconnecting from the network somehow corrupts the idle connections in okhttp
clients. New requests made over these clients fail. This commit works around that bug by
clearing the idle connection pool of each client when Android disconnects from the network.
roberthoenig added a commit to roberthoenig/react-native that referenced this issue Jun 25, 2018
…book#19709.

This bug is probably actually a bug in OkHttp: square/okhttp#4079
Both issues linked above contain extensive details about the issue, its likely origins and
how to reproduce it. A short summary of the issue and the fix in this commit:

On Android, disconnecting from the network somehow corrupts the idle connections in okhttp
clients. New requests made over these clients fail. This commit works around that bug by
clearing the idle connection pool of each client when Android disconnects from the network.
roberthoenig added a commit to roberthoenig/react-native that referenced this issue Jun 25, 2018
…book#19709.

This bug is probably actually a bug in OkHttp: square/okhttp#4079
Both issues linked above contain extensive details about the issue, its likely origins and
how to reproduce it. A short summary of the issue and the fix in this commit:

On Android, disconnecting from the network somehow corrupts the idle connections in okhttp
clients. New requests made over these clients fail. This commit works around that bug by
clearing the idle connection pool of each client when Android disconnects from the network.
roberthoenig added a commit to roberthoenig/react-native that referenced this issue Jun 27, 2018
…book#19709.

This bug is probably actually a bug in OkHttp: square/okhttp#4079
Both issues linked above contain extensive details about the issue, its likely origins and
how to reproduce it. A short summary of the issue and the fix in this commit:

On Android, disconnecting from the network somehow corrupts the idle connections and ongoing
calls in okhttp clients. New requests made over these clients fail. This commit works around
that bug by evicting the idle connection pool and cancelling all ongoing calls of each client
when we receive a DISCONNECTED or CONNECTING event (we don't know yet if only one or both of
them cause the issue).
Cancelling all calls is aggressive, but when a device disconnects any ongoing calls can fail
anyway, so an app has to expect this scenario.
roberthoenig added a commit to roberthoenig/react-native that referenced this issue Jun 27, 2018
…book#19709.

This bug is probably actually a bug in OkHttp: square/okhttp#4079
Both issues linked above contain extensive details about the issue, its likely origins and
how to reproduce it. A short summary of the issue and the fix in this commit:

On Android, disconnecting from the network somehow corrupts the idle connections and ongoing
calls in okhttp clients. New requests made over these clients fail. This commit works around
that bug by evicting the idle connection pool and cancelling all ongoing calls of each client
when we receive a DISCONNECTED or CONNECTING event (we don't know yet if only one or both of
them cause the issue).
Cancelling all calls is aggressive, but when a device disconnects any ongoing calls can fail
anyway, so an app has to expect this scenario.
roberthoenig added a commit to roberthoenig/react-native that referenced this issue Jun 28, 2018
…book#19709.

This bug is probably actually a bug in OkHttp: square/okhttp#4079
Both issues linked above contain extensive details about the issue,
its likely origins and how to reproduce it. A short summary of the
issue and the fix in this commit:

On Android, disconnecting from the network somehow corrupts the idle
connections and ongoing calls in okhttp clients. New requests made over
these clients fail. This commit works around that bug by evicting the idle
connection pool when we receive a DISCONNECTED or CONNECTING event (we
don't know yet if only one or both of them cause the issue). Technically,
to fully fix this issue, we would also need to cancel all ongoing calls.
However, cancelling all ongoing calls is aggressive, and not always desired
(when the app disconnects only for a short time, ongoing calls might still
succeed). In practice, just evicting idle connections results in this issue
occurring less often, so let's go with that for now.
roberthoenig added a commit to roberthoenig/react-native that referenced this issue Jun 28, 2018
…facebook#19709.

The previous commit partially fixed facebook#19709 by evicting all idle
connections on DISCONNECT and CONNECTING events. To fully fix facebook#19709, also
cancel all ongoing calls on these events. Cancel the ongoing calls
before evicting all idle connections, because cancelling the ongoing
calls can result in more idle connections.
@musicode
Copy link

I have the same issue.

@soumyamishra89
Copy link

I have not tried turning off and on the connection specifically. But i do face this issue. The fetch gets stuck without resolving. This happens over a weak internet connection and it takes forever for the fetch to resolve. Closing and reopening the app does solve the issue. Is there a solution in the works? I have seen similar issues 1 2 3 but no solution yet.

@stale
Copy link

stale bot commented Nov 14, 2018

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as "For Discussion" or "Good first issue" and I will leave it open. Thank you for your contributions.

@stale stale bot added the Stale There has been a lack of activity on this issue and it may be closed soon. label Nov 14, 2018
@soumyamishra89
Copy link

I have used a hack by keeping the connection idle for max 5 seconds. This has solved the issue to some extend. Have not faced the issue after this, but this is not an ideal solution. If anyone has some other solution please comment on this ticket.

@cpojer
Copy link
Contributor

cpojer commented Feb 12, 2019

Heads up: we moved react-native-netinfo into its own repository, which should allow for making changes and fixes much faster. Please continue the discussion about this issue there: react-native-netinfo/react-native-netinfo#11

@cpojer cpojer closed this as completed Feb 12, 2019
@cpojer
Copy link
Contributor

cpojer commented Feb 14, 2019

This was closed in error, so reopening it. Sorry about that!

@krtr
Copy link

krtr commented May 24, 2019

I'm experiencing this issue still on both xhr and fetch on android
react-native@0.59.8

@myunggyunSon
Copy link

I have a really hard time with this problem recently. I managed to find out the reason (fetch) though, but cannot get a clear solution. Any further progress on this issue?

@frank0r
Copy link

frank0r commented Oct 15, 2019

I use https://github.com/joltup/rn-fetch-blob#web-api-polyfills to replace official fetch API , so far so good, all of those issues were gone.

@stale
Copy link

stale bot commented Jan 13, 2020

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as a "Discussion" or add it to the "Backlog" and I will leave it open. Thank you for your contributions.

@stale stale bot added the Stale There has been a lack of activity on this issue and it may be closed soon. label Jan 13, 2020
@m96dy
Copy link

m96dy commented Jan 17, 2020

fix it please

@stale
Copy link

stale bot commented Apr 17, 2020

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as a "Discussion" or add it to the "Backlog" and I will leave it open. Thank you for your contributions.

@stale stale bot added the Stale There has been a lack of activity on this issue and it may be closed soon. label Apr 17, 2020
@giugrilli
Copy link

still having this issue with fetch and xhr

@stale stale bot removed the Stale There has been a lack of activity on this issue and it may be closed soon. label Apr 21, 2020
@stale
Copy link

stale bot commented Jul 25, 2020

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as a "Discussion" or add it to the "Backlog" and I will leave it open. Thank you for your contributions.

@stale stale bot added the Stale There has been a lack of activity on this issue and it may be closed soon. label Jul 25, 2020
@stale
Copy link

stale bot commented Aug 1, 2020

Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please feel free to create a new issue with up-to-date information.

@stale stale bot closed this as completed Aug 1, 2020
@mskarimi
Copy link

mskarimi commented Dec 6, 2021

same issue in react-native v0.67.0-rc.4
samsung a30 android 11

@iternio
Copy link

iternio commented Dec 29, 2021

Yes, this issue is still there. We have experienced it on a specific Android Automotive platform - only the physical device, not the AVD.

It seems to be related to a proxy or some middle layer in the networking stack on the Android which accepts connections even if the network is down but then does not return any results (obviously). That in combination with the implementation of NetworkingModule which only sets the connectTimeout in okhttp instead of the callTimeout seems to cause the calls to hang forever without running the error callback, meaning the JS Promise in RN will never return. AND in combination with the connectionPool, the same broken connection gets used for all subsequent calls.

The correct solution may be to change

    if (timeout != mClient.connectTimeoutMillis()) {
      clientBuilder.connectTimeout(timeout, TimeUnit.MILLISECONDS);
    }

in NetworkingModule.java into

    if (timeout != mClient.callTimeoutMillis()) {
      clientBuilder.callTimeout(timeout, TimeUnit.MILLISECONDS);
    }

but since we did not want to fork RN, we found another solution based on the original post which needed no modification of core RN:

The NetworkingModule accepts a plugged in CustomBuilder which exposes the okhttp state. Use that to insert a connectionPool().evictAll(). Add this to your apps MainApplication.java:

        @Override
        public ReactInstanceManager getReactInstanceManager() {
                NetworkingModule.setCustomClientBuilder(
                    builder -> {
                        builder.build().connectionPool().evictAll();
                        builder.retryOnConnectionFailure(false).connectTimeout(120, TimeUnit.SECONDS); // This may be optional
                    });
            return super.getReactInstanceManager();
        }

and it works like a charm.

@gianpaj
Copy link

gianpaj commented May 30, 2023

possible related issues in okhttp

square/okhttp#3278
square/okhttp#4079 (closed as duplicate of the above)

@JKKholmatov
Copy link

JKKholmatov commented Aug 29, 2023

@iternio @gianpaj @roberthoenig When I call fetch(url) sometimes it hangs for ~2-3 minutes but with VPN there is no issue, I gues it is relevant to this issue, but I am using Expo project, How can I solve this?

@Orange9000
Copy link

Yes, this issue is still there. We have experienced it on a specific Android Automotive platform - only the physical device, not the AVD.

It seems to be related to a proxy or some middle layer in the networking stack on the Android which accepts connections even if the network is down but then does not return any results (obviously). That in combination with the implementation of NetworkingModule which only sets the connectTimeout in okhttp instead of the callTimeout seems to cause the calls to hang forever without running the error callback, meaning the JS Promise in RN will never return. AND in combination with the connectionPool, the same broken connection gets used for all subsequent calls.

The correct solution may be to change

    if (timeout != mClient.connectTimeoutMillis()) {
      clientBuilder.connectTimeout(timeout, TimeUnit.MILLISECONDS);
    }

in NetworkingModule.java into

    if (timeout != mClient.callTimeoutMillis()) {
      clientBuilder.callTimeout(timeout, TimeUnit.MILLISECONDS);
    }

but since we did not want to fork RN, we found another solution based on the original post which needed no modification of core RN:

The NetworkingModule accepts a plugged in CustomBuilder which exposes the okhttp state. Use that to insert a connectionPool().evictAll(). Add this to your apps MainApplication.java:

        @Override
        public ReactInstanceManager getReactInstanceManager() {
                NetworkingModule.setCustomClientBuilder(
                    builder -> {
                        builder.build().connectionPool().evictAll();
                        builder.retryOnConnectionFailure(false).connectTimeout(120, TimeUnit.SECONDS); // This may be optional
                    });
            return super.getReactInstanceManager();
        }

and it works like a charm.

Huge thanks, it actually helped. But are there any potential downsides to clearing the connection pool? Is it being done conditionally (on timeout/connection failure) or perhaps before each API call is made?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug 🌐Networking Related to a networking API. Platform: Linux Building on Linux. Stale There has been a lack of activity on this issue and it may be closed soon.
Projects
None yet
Development

No branches or pull requests