From eca0577b41350c947ec93c2d76906717bee40ddf Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Mon, 24 Aug 2026 08:36:24 -0700 Subject: [PATCH] Give devsupport networking its own dispatcher and pool (#58094) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: `DevSupportHttpClient` derived its clients from the app's shared `OkHttpClient` with `newBuilder()`, which shares the underlying `Dispatcher` and `ConnectionPool`. Dev support keeps several long-lived WebSockets open to the dev server — the packager connection and the inspector among them — and a WebSocket occupies a running-call slot for its entire lifetime, because `RealWebSocket.loopReader` runs inside `RealCall.AsyncCall.execute`. OkHttp allows five concurrent calls per host by default, so those sockets throttle, and eventually stall, every other request to the dev server: `BundleDownloader` fetching the bundle itself, `PackagerStatusCheck`, and any application request that happens to target the same host. Give the devsupport clients a `Dispatcher` and a `ConnectionPool` of their own, still configured from the shared client so an `OkHttpClientFactory` override continues to apply. The per-host limit is raised on that dispatcher as well — without it the WebSockets would simply starve bundle traffic on the new dispatcher instead of the app's. Changelog: [Android][Fixed] - Stop dev server WebSockets from throttling bundle downloads and other dev server requests Reviewed By: cortinico Differential Revision: D117196401 --- .../inspector/DevSupportHttpClient.kt | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/DevSupportHttpClient.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/DevSupportHttpClient.kt index b1dc4a4e9a4a..547c8d803a0b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/DevSupportHttpClient.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/DevSupportHttpClient.kt @@ -11,17 +11,39 @@ package com.facebook.react.devsupport.inspector import com.facebook.react.modules.network.OkHttpClientProvider import java.util.concurrent.TimeUnit +import okhttp3.ConnectionPool +import okhttp3.Dispatcher import okhttp3.OkHttpClient /** - * Shared [OkHttpClient] instances for devsupport networking. Uses a single connection pool and - * dispatcher across all dev support HTTP and WebSocket usage. + * Shared [OkHttpClient] instances for devsupport networking. Configured from the app's client, so a + * [com.facebook.react.modules.network.OkHttpClientFactory] override still applies, but with a + * connection pool and dispatcher of their own, shared across all dev support HTTP and WebSocket + * usage and nothing else. + * + * The dispatcher must not be the app's. A WebSocket occupies a running-call slot on it for its + * whole lifetime — `RealWebSocket.loopReader` runs inside `RealCall.AsyncCall.execute` — and dev + * support keeps several open to one host: the packager connection, the inspector, and whatever else + * the app connects. OkHttp's default [Dispatcher.maxRequestsPerHost] is 5, so on the app's + * dispatcher those sockets throttle, and eventually stall, every other request to the dev server — + * including [com.facebook.react.devsupport.BundleDownloader] fetching the bundle itself. */ internal object DevSupportHttpClient { + // Enough that the long-lived WebSockets cannot crowd out bundle and asset traffic: the dev + // server is the only host this client talks to, so the per-host limit is the effective one. + private const val MAX_CONCURRENT_REQUESTS = 32 + /** Client for HTTP requests: connect=5s, write=disabled, read=disabled. */ internal val httpClient: OkHttpClient = OkHttpClientProvider.getOkHttpClient() .newBuilder() + .dispatcher( + Dispatcher().apply { + maxRequests = MAX_CONCURRENT_REQUESTS + maxRequestsPerHost = MAX_CONCURRENT_REQUESTS + }, + ) + .connectionPool(ConnectionPool()) .connectTimeout(5, TimeUnit.SECONDS) .writeTimeout(0, TimeUnit.MILLISECONDS) .readTimeout(0, TimeUnit.MINUTES)