Skip to content

Commit

Permalink
fix(devtools): add reconnect when websocket closed by server (#4129)
Browse files Browse the repository at this point in the history
* fix(devtools): add reconnect when websocket closed by server

* fix(devtools): fix the invalidation of source and memory after reconnecting for ios

* fix(devtools): make function name uppercase

* fix(devtools): change function name in jni
  • Loading branch information
Cyunong authored and wwwcg committed Dec 11, 2024
1 parent 4d8ba71 commit 885593b
Show file tree
Hide file tree
Showing 13 changed files with 67 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ namespace hippy::devtools {
class DevtoolsBackendService : public std::enable_shared_from_this<DevtoolsBackendService> {
public:
DevtoolsBackendService(const DevtoolsConfig& devtools_config,
std::shared_ptr<footstone::WorkerManager> worker_manager);
std::shared_ptr<footstone::WorkerManager> worker_manager, std::function<void()> reconnect_handler);

~DevtoolsBackendService();

Expand Down
3 changes: 2 additions & 1 deletion devtools/devtools-backend/include/tunnel/net_channel.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ constexpr uint8_t kTaskFlag = 210; // message flag
class NetChannel {
public:
using ReceiveDataHandler = std::function<void(const std::string& msg, uint8_t flag)>;
using ReconnectHandler = std::function<void()>;

virtual ~NetChannel() {}

/**
* @brief connect to frontend
* @param handler to receive msg from frontend
*/
virtual void Connect(ReceiveDataHandler handler) = 0;
virtual void Connect(ReceiveDataHandler handler, ReconnectHandler reconnect_handler) = 0;

/**
* @brief send data to frontend
Expand Down
2 changes: 1 addition & 1 deletion devtools/devtools-backend/include/tunnel/tcp/tcp_channel.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ constexpr int32_t kBufferSize = 32 * 1024;
class TcpChannel : public hippy::devtools::NetChannel, public std::enable_shared_from_this<TcpChannel> {
public:
TcpChannel();
void Connect(ReceiveDataHandler handler) override;
void Connect(ReceiveDataHandler handler, ReconnectHandler reconnect_handler) override;
void Send(const std::string& data) override;
void Close(int32_t code, const std::string& reason) override;

Expand Down
2 changes: 1 addition & 1 deletion devtools/devtools-backend/include/tunnel/tunnel_service.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class TunnelService : public std::enable_shared_from_this<TunnelService> {
/**
* @brief connect to frontend
*/
void Connect();
void Connect(std::function<void()> reconnect_handler);

/**
* @brief send data to frontend
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,24 +49,28 @@ namespace hippy::devtools {
class WebSocketChannel : public hippy::devtools::NetChannel, public std::enable_shared_from_this<WebSocketChannel> {
public:
explicit WebSocketChannel(const std::string& ws_uri);
void Connect(ReceiveDataHandler handler) override;
void Connect(ReceiveDataHandler handler, ReconnectHandler reconnect_handler) override;
void Send(const std::string& rsp_data) override;
void Close(int32_t code, const std::string& reason) override;

private:
void StartConnect(const std::string& ws_uri);
void HandleSocketInit(const websocketpp::connection_hdl& handle);
void HandleSocketConnectFail(const websocketpp::connection_hdl& handle);
void HandleSocketConnectOpen(const websocketpp::connection_hdl& handle);
void HandleSocketConnectOpen(const websocketpp::connection_hdl& handle, ReconnectHandler reconnect_handler);
void HandleSocketConnectMessage(const websocketpp::connection_hdl& handle, const WSMessagePtr& message_ptr);
void HandleSocketConnectClose(const websocketpp::connection_hdl& handle);
void AttemptReconnect();

WSClient ws_client_;
websocketpp::connection_hdl connection_hdl_;
std::string ws_uri_;
ReceiveDataHandler data_handler_;
WSThread ws_thread_;
std::vector<std::string> unset_messages_{};
bool ws_should_reconnect;
static const int MAX_RECONNECT_ATTEMPTS = 10;
int ws_reconnect_attempts;
};
} // namespace hippy::devtools

Expand Down
5 changes: 3 additions & 2 deletions devtools/devtools-backend/src/api/devtools_backend_service.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@

namespace hippy::devtools {
DevtoolsBackendService::DevtoolsBackendService(const DevtoolsConfig& devtools_config,
std::shared_ptr<footstone::WorkerManager> worker_manager) {
std::shared_ptr<footstone::WorkerManager> worker_manager,
std::function<void()> reconnect_handler) {
FOOTSTONE_DLOG(INFO) << kDevToolsTag << "DevtoolsBackendService create framework:" << devtools_config.framework
<< ",tunnel:" << devtools_config.tunnel;
auto data_provider = std::make_shared<DataProvider>();
Expand All @@ -38,7 +39,7 @@ DevtoolsBackendService::DevtoolsBackendService(const DevtoolsConfig& devtools_co
domain_dispatch_ = std::make_shared<DomainDispatch>(data_channel_, worker_manager);
domain_dispatch_->RegisterDefaultDomainListener();
tunnel_service_ = std::make_shared<TunnelService>(domain_dispatch_, devtools_config);
tunnel_service_->Connect();
tunnel_service_->Connect(reconnect_handler);
notification_center->runtime_notification = std::make_shared<DefaultRuntimeNotification>(tunnel_service_);

if (devtools_config.framework == Framework::kHippy) {
Expand Down
3 changes: 2 additions & 1 deletion devtools/devtools-backend/src/tunnel/tcp/tcp_channel.cc
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ TcpChannel::TcpChannel() {
frame_codec_ = FrameCodec();
}

void TcpChannel::Connect(ReceiveDataHandler handler) {
void TcpChannel::Connect(ReceiveDataHandler handler, ReconnectHandler reconnect_handler) {
// TODO: reconnect in TCP if needed
frame_codec_.SetEncodeCallback([WEAK_THIS](void *data, int32_t len) {
DEFINE_AND_CHECK_SELF(TcpChannel)
if (self->client_fd_ < 0) {
Expand Down
4 changes: 2 additions & 2 deletions devtools/devtools-backend/src/tunnel/tunnel_service.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ TunnelService::TunnelService(std::shared_ptr<DomainDispatch> dispatch, const Dev
channel_ = NetChannel::CreateChannel(devtools_config);
}

void TunnelService::Connect() {
void TunnelService::Connect(std::function<void()> reconnect_handler) {
FOOTSTONE_DLOG(INFO) << kDevToolsTag << "TunnelService, start connect.";
channel_->Connect([WEAK_THIS](const std::string& msg, int flag) {
if (flag == kTaskFlag) {
DEFINE_AND_CHECK_SELF(TunnelService)
self->HandleReceiveData(msg);
}
});
}, reconnect_handler);
dispatch_->SetResponseHandler([WEAK_THIS](const std::string &rsp_data) {
DEFINE_AND_CHECK_SELF(TunnelService)
self->channel_->Send(rsp_data);
Expand Down
40 changes: 33 additions & 7 deletions devtools/devtools-backend/src/tunnel/ws/web_socket_channel.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ typedef WSClient::connection_ptr WSConnectionPtr;
namespace hippy::devtools {

WebSocketChannel::WebSocketChannel(const std::string& ws_uri) {
ws_reconnect_attempts = 0;
ws_should_reconnect = true;
ws_uri_ = ws_uri;
ws_client_.clear_access_channels(websocketpp::log::alevel::all);
ws_client_.set_access_channels(websocketpp::log::alevel::fail);
Expand All @@ -43,7 +45,7 @@ WebSocketChannel::WebSocketChannel(const std::string& ws_uri) {
ws_client_.start_perpetual();
}

void WebSocketChannel::Connect(ReceiveDataHandler handler) {
void WebSocketChannel::Connect(ReceiveDataHandler handler, ReconnectHandler reconnect_handler) {
if (ws_uri_.empty()) {
FOOTSTONE_DLOG(ERROR) << kDevToolsTag << "websocket uri is empty, connect error";
return;
Expand All @@ -55,9 +57,9 @@ void WebSocketChannel::Connect(ReceiveDataHandler handler) {
DEFINE_AND_CHECK_SELF(WebSocketChannel)
self->HandleSocketInit(handle);
});
ws_client_.set_open_handler([WEAK_THIS](const websocketpp::connection_hdl& handle) {
ws_client_.set_open_handler([WEAK_THIS, reconnect_handler](const websocketpp::connection_hdl& handle) {
DEFINE_AND_CHECK_SELF(WebSocketChannel)
self->HandleSocketConnectOpen(handle);
self->HandleSocketConnectOpen(handle, reconnect_handler);
});
ws_client_.set_close_handler([WEAK_THIS](const websocketpp::connection_hdl& handle) {
DEFINE_AND_CHECK_SELF(WebSocketChannel)
Expand Down Expand Up @@ -87,6 +89,7 @@ void WebSocketChannel::Send(const std::string& rsp_data) {
}

void WebSocketChannel::Close(int32_t code, const std::string& reason) {
ws_should_reconnect = false;
if (!connection_hdl_.lock()) {
FOOTSTONE_DLOG(ERROR) << kDevToolsTag << "send message error, handler is null";
return;
Expand Down Expand Up @@ -128,7 +131,8 @@ void WebSocketChannel::HandleSocketConnectFail(const websocketpp::connection_hdl
<< ", remote close reason:" << con->get_remote_close_reason().c_str();
}

void WebSocketChannel::HandleSocketConnectOpen(const websocketpp::connection_hdl& handle) {
void WebSocketChannel::HandleSocketConnectOpen(const websocketpp::connection_hdl& handle,
ReconnectHandler reconnect_handler) {
connection_hdl_ = handle.lock();
FOOTSTONE_DLOG(INFO) << kDevToolsTag << "websocket connect open";
if (!connection_hdl_.lock() || unset_messages_.empty()) {
Expand All @@ -139,6 +143,10 @@ void WebSocketChannel::HandleSocketConnectOpen(const websocketpp::connection_hdl
ws_client_.send(connection_hdl_, message, websocketpp::frame::opcode::text, error_code);
}
unset_messages_.clear();
if (0 < ws_reconnect_attempts && ws_reconnect_attempts < MAX_RECONNECT_ATTEMPTS) {
reconnect_handler();
}
ws_reconnect_attempts = 0;
}

void WebSocketChannel::HandleSocketConnectMessage(const websocketpp::connection_hdl& handle,
Expand All @@ -153,14 +161,32 @@ void WebSocketChannel::HandleSocketConnectMessage(const websocketpp::connection_
void WebSocketChannel::HandleSocketConnectClose(const websocketpp::connection_hdl& handle) {
websocketpp::lib::error_code error_code;
auto con = ws_client_.get_con_from_hdl(handle, error_code);
// set handle nullptr when connect fail
data_handler_ = nullptr;
unset_messages_.clear();
FOOTSTONE_DLOG(INFO) << kDevToolsTag << "websocket connect close, state: " << con->get_state()
<< ", error message:" << con->get_ec().message().c_str()
<< ", local close code:" << con->get_local_close_code()
<< ", local close reason: " << con->get_local_close_reason().c_str()
<< ", remote close code:" << con->get_remote_close_code()
<< ", remote close reason:" << con->get_remote_close_reason().c_str();
if (ws_should_reconnect) {
AttemptReconnect();
}
else {
// set handle nullptr when connect fail
data_handler_ = nullptr;
unset_messages_.clear();
}
}

void WebSocketChannel::AttemptReconnect() {
ws_reconnect_attempts++;
if (ws_reconnect_attempts < MAX_RECONNECT_ATTEMPTS) {
FOOTSTONE_DLOG(INFO) << "Attempting to reconnect (" << ws_reconnect_attempts << "/"
<< MAX_RECONNECT_ATTEMPTS << ")...";
StartConnect(ws_uri_);
} else {
ws_should_reconnect = false;
FOOTSTONE_DLOG(INFO) << "Max reconnect attempts reached.";
}
}

} // namespace hippy::devtools
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ jint OnCreateDevtools(JNIEnv* j_env,
const string_view ws_url = JniUtils::ToStrView(j_env, j_ws_url);
DevtoolsDataSource::SetFileCacheDir(StringViewUtils::ToStdString(
StringViewUtils::ConvertEncoding(data_dir, string_view::Encoding::Utf8).utf8_value()));
auto devtools_data_source = std::make_shared<hippy::devtools::DevtoolsDataSource>(
auto devtools_data_source = std::make_shared<hippy::devtools::DevtoolsDataSource>();
devtools_data_source->CreateDevtoolsService(
StringViewUtils::ToStdString(StringViewUtils::ConvertEncoding(ws_url, string_view::Encoding::Utf8).utf8_value()),
worker_manager);
uint32_t id = devtools::DevtoolsDataSource::Insert(devtools_data_source);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,14 @@ namespace hippy::devtools {
*/
class DevtoolsDataSource : public std::enable_shared_from_this<hippy::devtools::DevtoolsDataSource> {
public:
DevtoolsDataSource() = default;
~DevtoolsDataSource() = default;
/**
* create devtools data source instance
* create devtools service
* @param ws_url websocket url, if empty then use other tunnel
* @param worker_manager worker thread for devtools
*/
DevtoolsDataSource(const std::string& ws_url, std::shared_ptr<footstone::WorkerManager> worker_manager);
~DevtoolsDataSource() = default;
void CreateDevtoolsService(const std::string& ws_url, std::shared_ptr<footstone::WorkerManager> worker_manager);
/**
* @brief bind dom, so that devtools can access and collect data
*/
Expand Down Expand Up @@ -110,5 +111,6 @@ class DevtoolsDataSource : public std::enable_shared_from_this<hippy::devtools::
std::shared_ptr<HippyDomData> hippy_dom_;
uint64_t listener_id_{};
std::shared_ptr<hippy::devtools::DevtoolsBackendService> devtools_service_;
std::string context_name_;
};
} // namespace hippy::devtools
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ using StringViewUtils = footstone::stringview::StringViewUtils;
static std::atomic<uint32_t> global_devtools_data_key{1};
footstone::utils::PersistentObjectMap<uint32_t, std::shared_ptr<DevtoolsDataSource>> devtools_data_map;

DevtoolsDataSource::DevtoolsDataSource(const std::string& ws_url,
void DevtoolsDataSource::CreateDevtoolsService(const std::string& ws_url,
std::shared_ptr<footstone::WorkerManager> worker_manager) {
hippy::devtools::DevtoolsConfig devtools_config;
devtools_config.framework = hippy::devtools::Framework::kHippy;
Expand All @@ -53,7 +53,11 @@ DevtoolsDataSource::DevtoolsDataSource(const std::string& ws_url,
} else { // empty websocket url, then use tcp tunnel by usb channel
devtools_config.tunnel = hippy::devtools::Tunnel::kTcp;
}
devtools_service_ = std::make_shared<hippy::devtools::DevtoolsBackendService>(devtools_config, worker_manager);
auto reconnect_handler = [WEAK_THIS] {
DEFINE_AND_CHECK_SELF(DevtoolsDataSource)
self->SetContextName(self->context_name_);
};
devtools_service_ = std::make_shared<hippy::devtools::DevtoolsBackendService>(devtools_config, worker_manager, reconnect_handler);
devtools_service_->Create();
hippy_dom_ = std::make_shared<HippyDomData>();
}
Expand All @@ -78,6 +82,7 @@ void DevtoolsDataSource::Destroy(bool is_reload) {
}

void DevtoolsDataSource::SetContextName(const std::string& context_name) {
context_name_ = context_name;
GetNotificationCenter()->runtime_notification->UpdateContextName(context_name);
}

Expand Down
3 changes: 2 additions & 1 deletion framework/ios/base/executors/HippyJSExecutor.mm
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ - (void)setup {
NSString *wsURL = [self completeWSURLWithBridge:bridge];
if (wsURL.length > 0) {
auto workerManager = std::make_shared<footstone::WorkerManager>(1);
auto devtools_data_source = std::make_shared<hippy::devtools::DevtoolsDataSource>([wsURL UTF8String], workerManager);
auto devtools_data_source = std::make_shared<hippy::devtools::DevtoolsDataSource>();
devtools_data_source->CreateDevtoolsService([wsURL UTF8String], workerManager);
self.pScope->SetDevtoolsDataSource(devtools_data_source);
}
}
Expand Down

0 comments on commit 885593b

Please sign in to comment.