From 8eca26fa4cbf8107a17419dfce23ce7096090d22 Mon Sep 17 00:00:00 2001 From: Shubham Kumar Savita Date: Wed, 29 Apr 2026 09:24:36 -0700 Subject: [PATCH] Add exponential backoff to InspectorPackagerConnection reconnect and exception handling to connect (#56650) Summary: ## Summary Fix occasional SIGSEGV in `operator new` during `InspectorPackagerConnection::connectWebSocket()`. ### Root Cause The `InspectorPackagerConnection` reconnection loop retries every 2 seconds with no backoff or limit. When no Metro dev server is reachable, this results in hundreds of failed WebSocket connection attempts over the app lifetime. Each attempt allocates and deallocates C++ hybrid objects, JNI references, and OkHttp WebSocket instances. This accumulated churn leads to heap fragmentation and eventual heap metadata corruption, manifesting as SIGSEGV in `operator new` when the allocator follows corrupted internal pointers. Additionally, the `connect()` method had no exception handling around the `connectWebSocket()` call, meaning any JNI/Java exception would propagate uncaught. ### Changes 1. **Exponential backoff in `reconnect()`**: Start at 2s, double each retry, cap at 120s. This reduces reconnection attempts from ~500 in 17 minutes to ~15, drastically reducing resource churn. 2. **Try-catch in `connect()`**: Catch exceptions from `connectWebSocket()` (e.g., fbjni/JNI exceptions) and gracefully trigger a reconnect instead of crashing. 3. **Reset backoff on success**: When `didOpen()` fires (successful connection), reset the delay back to 2s and clear `suppressConnectionErrors_`. ## Changelog: [General][Fixed] - Add exponential backoff and exception handling to InspectorPackagerConnection reconnect loop to prevent heap fragmentation crashes Reviewed By: cipolleschi Differential Revision: D100956687 --- .../InspectorPackagerConnection.cpp | 21 +++++++++++++++++-- .../InspectorPackagerConnectionImpl.h | 3 +++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/react-native/ReactCommon/jsinspector-modern/InspectorPackagerConnection.cpp b/packages/react-native/ReactCommon/jsinspector-modern/InspectorPackagerConnection.cpp index e2ea5b908bf..4edf6e0c6e8 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/InspectorPackagerConnection.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/InspectorPackagerConnection.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include using namespace std::literals; @@ -22,6 +23,7 @@ namespace facebook::react::jsinspector_modern { static constexpr const std::chrono::duration RECONNECT_DELAY = std::chrono::milliseconds{2000}; +static constexpr const std::chrono::milliseconds MAX_RECONNECT_DELAY{120000}; static constexpr const char* INVALID = ""; // InspectorPackagerConnection::Impl method definitions @@ -301,6 +303,8 @@ void InspectorPackagerConnection::Impl::didReceiveMessage( void InspectorPackagerConnection::Impl::didOpen() { connected_ = true; + reconnectDelayMs_ = RECONNECT_DELAY; + suppressConnectionErrors_ = false; } void InspectorPackagerConnection::Impl::didClose() { @@ -333,7 +337,15 @@ void InspectorPackagerConnection::Impl::connect() { << "Illegal state: Can't connect after having previously been closed."; return; } - webSocket_ = delegate_->connectWebSocket(url_, weak_from_this()); + try { + webSocket_ = delegate_->connectWebSocket(url_, weak_from_this()); + } catch (const std::exception& e) { + LOG(ERROR) << "Failed to create WebSocket connection: " << e.what(); + reconnect(); + } catch (...) { + LOG(ERROR) << "Failed to create WebSocket connection: unknown error"; + reconnect(); + } } void InspectorPackagerConnection::Impl::reconnect() { @@ -357,6 +369,11 @@ void InspectorPackagerConnection::Impl::reconnect() { reconnectPending_ = true; + auto currentDelay = reconnectDelayMs_; + + // Exponential backoff: double the delay for next time, up to the max. + reconnectDelayMs_ = std::min(reconnectDelayMs_ * 2, MAX_RECONNECT_DELAY); + delegate_->scheduleCallback( [weakSelf = weak_from_this()] { auto strongSelf = weakSelf.lock(); @@ -370,7 +387,7 @@ void InspectorPackagerConnection::Impl::reconnect() { strongSelf->connect(); } }, - RECONNECT_DELAY); + currentDelay); } void InspectorPackagerConnection::Impl::closeQuietly() { diff --git a/packages/react-native/ReactCommon/jsinspector-modern/InspectorPackagerConnectionImpl.h b/packages/react-native/ReactCommon/jsinspector-modern/InspectorPackagerConnectionImpl.h index 560e7c2129a..d669bfd5ac6 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/InspectorPackagerConnectionImpl.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/InspectorPackagerConnectionImpl.h @@ -132,6 +132,9 @@ class InspectorPackagerConnection::Impl : public IWebSocketDelegate, // Whether a reconnection is currently pending. bool reconnectPending_{false}; + // Current reconnect delay in milliseconds, used for exponential backoff. + std::chrono::milliseconds reconnectDelayMs_{2000}; + SessionId nextSessionId_{1}; };