Skip to content

Commit 882be91

Browse files
javachemeta-codesync[bot]
authored andcommitted
Auto-reconnect PackagerConnection to Metro dev server (facebook#56625)
Summary: Pull Request resolved: facebook#56625 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 fbshipit-source-id: b195b8f67713e690279f4f96fb95264b125630c4
1 parent 5b3a1a4 commit 882be91

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
@@ -398,7 +398,6 @@ bool ReactHost::loadScriptFromDevServer() {
398398
devServerHelper_->setupHMRClient();
399399
return true;
400400
} catch (...) {
401-
devServerHelper_->setSourcePath("");
402401
LOG(WARNING)
403402
<< "Unable to download JS bundle from Metro, falling back to prebuilt JS bundle. "
404403
<< "To start Metro, run in command line: 'cd ~/fbsource/xplat/js && js1 run'";

0 commit comments

Comments
 (0)