From aa2e00a6b28bd0867228da35daa6168f27b56769 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Robert=20H=C3=B6nig?= <robhoenig@gmail.com>
Date: Fri, 22 Jun 2018 17:17:26 +0200
Subject: [PATCH 1/2] networking: Fix requests failing when turning network off
 and on #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.
---
 .../com/facebook/react/ReactActivity.java     |  8 +++
 .../modules/network/OkHttpClientProvider.java | 62 ++++++++++++++++++-
 2 files changed, 69 insertions(+), 1 deletion(-)

diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java b/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
index bbe9cad44738..afe65b7fcdfe 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
@@ -9,14 +9,18 @@
 
 import javax.annotation.Nullable;
 
+import android.Manifest;
 import android.app.Activity;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.os.Bundle;
+import android.support.v4.content.ContextCompat;
 import android.view.KeyEvent;
 
 import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
 import com.facebook.react.modules.core.PermissionAwareActivity;
 import com.facebook.react.modules.core.PermissionListener;
+import com.facebook.react.modules.network.OkHttpClientProvider;
 
 /**
  * Base Activity for React Native applications.
@@ -50,6 +54,10 @@ protected ReactActivityDelegate createReactActivityDelegate() {
   protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     mDelegate.onCreate(savedInstanceState);
+    if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_NETWORK_STATE)
+        == PackageManager.PERMISSION_GRANTED) {
+      OkHttpClientProvider.addNetworkListenerToEvictIdleConnectionsOnNetworkChange(getApplicationContext());
+    }
   }
 
   @Override
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpClientProvider.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpClientProvider.java
index 766290c40e06..d75543a3eac7 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpClientProvider.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpClientProvider.java
@@ -7,12 +7,22 @@
 
 package com.facebook.react.modules.network;
 
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
 import android.os.Build;
+import android.os.Bundle;
 
 import com.facebook.common.logging.FLog;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.Set;
+import java.util.WeakHashMap;
 import java.util.concurrent.TimeUnit;
 
 import javax.annotation.Nullable;
@@ -33,6 +43,9 @@ public class OkHttpClientProvider {
   // User-provided OkHttpClient factory
   private static @Nullable OkHttpClientFactory sFactory;
 
+  private final static Set<OkHttpClient> sClients = Collections.newSetFromMap(
+    new WeakHashMap<OkHttpClient, Boolean>());
+
   public static void setOkHttpClientFactory(OkHttpClientFactory factory) {
     sFactory = factory;
   }
@@ -43,6 +56,47 @@ public static OkHttpClient getOkHttpClient() {
     }
     return sClient;
   }
+
+  /*
+    See https://github.com/facebook/react-native/issues/19709 for context.
+    We know that connections get corrupted when the connectivity state
+    changes to disconnected, but the debugging of this issue hasn't been
+    exhaustive and it's possible that other changes in connectivity also
+    corrupt idle connections. `CONNECTIVITY_ACTION`s occur infrequently
+    enough to go safe and evict all idle connections and ongoing calls
+    for the events DISCONNECTED and CONNECTING. Don't do this for CONNECTED
+    since it's possible that new calls have already been dispatched by the
+    time we receive the event.
+   */
+  public static void addNetworkListenerToEvictIdleConnectionsOnNetworkChange(Context context) {
+    final BroadcastReceiver br = new BroadcastReceiver() {
+      @Override
+      public void onReceive(Context context, Intent intent) {
+        if (!intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+          return;
+        }
+        final Bundle extras = intent.getExtras();
+        final NetworkInfo info = extras.getParcelable("networkInfo");
+        final NetworkInfo.State state = info.getState();
+        if (state == NetworkInfo.State.CONNECTED) {
+          return;
+        }
+        final PendingResult result = goAsync();
+        final Thread thread = new Thread() {
+          public void run() {
+              for (OkHttpClient client: sClients) {
+                client.connectionPool().evictAll();
+              }
+              result.finish();
+          }
+        };
+        thread.start();
+      }
+    };
+    final IntentFilter intentFilter = new IntentFilter();
+    intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+    context.registerReceiver(br, intentFilter);
+  }
   
   // okhttp3 OkHttpClient is immutable
   // This allows app to init an OkHttpClient with custom settings.
@@ -54,7 +108,9 @@ public static OkHttpClient createClient() {
     if (sFactory != null) {
       return sFactory.createNewNetworkModuleClient();
     }
-    return createClientBuilder().build();
+    final OkHttpClient client = createClientBuilder().build();
+    registerClientToEvictIdleConnectionsOnNetworkChange(client);
+    return client;
   }
 
   public static OkHttpClient.Builder createClientBuilder() {
@@ -68,6 +124,10 @@ public static OkHttpClient.Builder createClientBuilder() {
     return enableTls12OnPreLollipop(client);
   }
 
+  public static void registerClientToEvictIdleConnectionsOnNetworkChange(OkHttpClient client) {
+    sClients.add(client);
+  }
+
   /*
     On Android 4.1-4.4 (API level 16 to 19) TLS 1.1 and 1.2 are
     available but not enabled by default. The following method

From 4480679102f5c03fab11280f4b0346fe4d1f12c8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Robert=20H=C3=B6nig?= <robhoenig@gmail.com>
Date: Thu, 28 Jun 2018 11:24:18 +0200
Subject: [PATCH 2/2] networking: Fully fix requests failing when turning
 network off and on #19709.

The previous commit partially fixed #19709 by evicting all idle
connections on DISCONNECT and CONNECTING events. To fully fix #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.
---
 .../com/facebook/react/modules/network/OkHttpClientProvider.java | 1 +
 1 file changed, 1 insertion(+)

diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpClientProvider.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpClientProvider.java
index d75543a3eac7..209c9a592e74 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpClientProvider.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpClientProvider.java
@@ -85,6 +85,7 @@ public void onReceive(Context context, Intent intent) {
         final Thread thread = new Thread() {
           public void run() {
               for (OkHttpClient client: sClients) {
+                client.dispatcher().cancelAll();
                 client.connectionPool().evictAll();
               }
               result.finish();
