Skip to content

Commit 41b719b

Browse files
javachefacebook-github-bot
authored andcommitted
Auto-reconnect PackagerConnection to Metro dev server (#56625)
Summary: When MWA starts before Metro is running, PackagerConnection's WebSocket connection to Metro's `/message` endpoint silently fails with no retry. The developer must restart MWA after starting Metro to get HMR working. This diff adds reconnection logic to PackagerConnection so it automatically retries when Metro becomes available. On initial connect failure or WebSocket disconnect, PackagerConnection schedules a retry after 5 seconds using the existing `WebSocketClientFactory` to create a fresh client. On successful reconnect, it fires `liveReloadCallback_()` which triggers `FoxReactHost::reloadReactInstance()` to load the bundle from Metro and set up HMR. The reconnection is event-driven: each retry is a short-lived thread that sleeps 5 seconds then calls `attemptConnection()`. The connect callback handles success/failure — no long-lived polling thread. The `active_` flag is set to false in the destructor to stop retries during shutdown. Combined with D97570592's push-based route invalidation, this completes the dev loop: Metro can start at any time, PackagerConnection auto-reconnects, HMR keeps the bundle fresh, and route changes propagate automatically. Changelog: [Internal] Reviewed By: christophpurrer Differential Revision: D97823787
1 parent c4f94c7 commit 41b719b

3 files changed

Lines changed: 67 additions & 13 deletions

File tree

packages/react-native/ReactCxxPlatform/react/devsupport/PackagerConnection.cpp

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,40 @@
99

1010
#include <glog/logging.h>
1111
#include <nlohmann/json.hpp>
12-
#include <utility>
1312

1413
namespace facebook::react {
1514

1615
PackagerConnection::PackagerConnection(
17-
const WebSocketClientFactory& webSocketClientFactory,
18-
const std::string& packagerConnectionUrl,
16+
WebSocketClientFactory webSocketClientFactory,
17+
std::string packagerConnectionUrl,
1918
LiveReloadCallback&& liveReloadCallback,
2019
ShowDevMenuCallback&& showDevMenuCallback)
21-
: liveReloadCallback_(std::move(liveReloadCallback)),
20+
: webSocketClientFactory_(std::move(webSocketClientFactory)),
21+
packagerConnectionUrl_(std::move(packagerConnectionUrl)),
22+
liveReloadCallback_(std::move(liveReloadCallback)),
2223
showDevMenuCallback_(std::move(showDevMenuCallback)) {
23-
websocket_ = webSocketClientFactory();
24+
attemptConnection();
25+
}
26+
27+
PackagerConnection::~PackagerConnection() noexcept {
28+
reconnectThread_.quit();
29+
if (websocket_) {
30+
websocket_->setOnClosedCallback(nullptr);
31+
websocket_->setOnMessageCallback(nullptr);
32+
websocket_->close("PackagerConnection destroyed");
33+
}
34+
}
35+
36+
void PackagerConnection::attemptConnection() {
37+
if (websocket_) {
38+
websocket_->setOnClosedCallback(nullptr);
39+
websocket_->close("reconnecting");
40+
}
41+
websocket_ = webSocketClientFactory_();
2442
websocket_->setOnMessageCallback([this](const std::string& message) {
2543
LOG(INFO) << "Received message from packager: " << message;
26-
auto json = nlohmann::json::parse(message);
27-
if (json.is_null() || json["version"] != 2) {
44+
auto json = nlohmann::json::parse(message, nullptr, false);
45+
if (json.is_discarded() || json.is_null() || json["version"] != 2) {
2846
return;
2947
}
3048
auto method = json["method"];
@@ -34,11 +52,38 @@ PackagerConnection::PackagerConnection(
3452
showDevMenuCallback_();
3553
}
3654
});
37-
websocket_->connect(packagerConnectionUrl);
55+
websocket_->setOnClosedCallback([this](const std::string& reason) {
56+
LOG(INFO) << "PackagerConnection closed: " << reason;
57+
scheduleReconnect();
58+
});
59+
websocket_->connect(
60+
packagerConnectionUrl_,
61+
[this](bool success, const std::string& /*error*/) {
62+
if (success) {
63+
if (!isInitialConnection_) {
64+
LOG(INFO)
65+
<< "PackagerConnection connected to Metro - triggering live reload";
66+
liveReloadCallback_();
67+
} else {
68+
LOG(INFO) << "PackagerConnection connected to Metro";
69+
}
70+
} else {
71+
scheduleReconnect();
72+
}
73+
isInitialConnection_ = false;
74+
});
3875
}
3976

40-
PackagerConnection::~PackagerConnection() noexcept {
41-
websocket_->close("PackagerConnection destroyed");
77+
void PackagerConnection::scheduleReconnect() {
78+
if (reconnectPending_.exchange(true)) {
79+
return;
80+
}
81+
reconnectThread_.runAsync(
82+
[this]() {
83+
reconnectPending_ = false;
84+
attemptConnection();
85+
},
86+
std::chrono::milliseconds(5000));
4287
}
4388

4489
} // namespace facebook::react

packages/react-native/ReactCxxPlatform/react/devsupport/PackagerConnection.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#pragma once
99

1010
#include <react/http/IWebSocketClient.h>
11+
#include <react/threading/TaskDispatchThread.h>
12+
#include <atomic>
1113
#include <functional>
1214
#include <memory>
1315
#include <string>
@@ -20,8 +22,8 @@ class PackagerConnection {
2022

2123
public:
2224
PackagerConnection(
23-
const WebSocketClientFactory &webSocketClientFactory,
24-
const std::string &packagerConnectionUrl,
25+
WebSocketClientFactory webSocketClientFactory,
26+
std::string packagerConnectionUrl,
2527
LiveReloadCallback &&liveReloadCallback,
2628
ShowDevMenuCallback &&showDevMenuCallback);
2729
~PackagerConnection() noexcept;
@@ -31,9 +33,17 @@ class PackagerConnection {
3133
PackagerConnection &operator=(PackagerConnection &&other) = delete;
3234

3335
private:
36+
void attemptConnection();
37+
void scheduleReconnect();
38+
39+
const WebSocketClientFactory webSocketClientFactory_;
40+
const std::string packagerConnectionUrl_;
3441
const LiveReloadCallback liveReloadCallback_;
3542
const ShowDevMenuCallback showDevMenuCallback_;
3643
std::unique_ptr<IWebSocketClient> websocket_;
44+
std::atomic<bool> isInitialConnection_{true};
45+
std::atomic<bool> reconnectPending_{false};
46+
TaskDispatchThread reconnectThread_{"PackagerReconnect"};
3747
};
3848

3949
} // namespace facebook::react

packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,6 @@ bool ReactHost::loadScriptFromDevServer() {
396396
devServerHelper_->setupHMRClient();
397397
return true;
398398
} catch (...) {
399-
devServerHelper_->setSourcePath("");
400399
LOG(WARNING)
401400
<< "Unable to download JS bundle from Metro, falling back to prebuilt JS bundle. "
402401
<< "To start Metro, run in command line: 'cd ~/fbsource/xplat/js && js1 run'";

0 commit comments

Comments
 (0)