From 885593b31f8697573fb5216ab66c50513e5ab677 Mon Sep 17 00:00:00 2001 From: arnonchen <52275454+Cyunong@users.noreply.github.com> Date: Fri, 22 Nov 2024 14:52:37 +0800 Subject: [PATCH] fix(devtools): add reconnect when websocket closed by server (#4129) * 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 --- .../include/api/devtools_backend_service.h | 2 +- .../include/tunnel/net_channel.h | 3 +- .../include/tunnel/tcp/tcp_channel.h | 2 +- .../include/tunnel/tunnel_service.h | 2 +- .../include/tunnel/ws/web_socket_channel.h | 8 +++- .../src/api/devtools_backend_service.cc | 5 ++- .../src/tunnel/tcp/tcp_channel.cc | 3 +- .../src/tunnel/tunnel_service.cc | 4 +- .../src/tunnel/ws/web_socket_channel.cc | 40 +++++++++++++++---- .../android/src/main/cpp/src/devtools_jni.cc | 3 +- .../include/devtools/devtools_data_source.h | 8 ++-- .../native/src/devtools_data_source.cc | 9 ++++- .../ios/base/executors/HippyJSExecutor.mm | 3 +- 13 files changed, 67 insertions(+), 25 deletions(-) diff --git a/devtools/devtools-backend/include/api/devtools_backend_service.h b/devtools/devtools-backend/include/api/devtools_backend_service.h index 9b157504e72..98979963eb6 100644 --- a/devtools/devtools-backend/include/api/devtools_backend_service.h +++ b/devtools/devtools-backend/include/api/devtools_backend_service.h @@ -42,7 +42,7 @@ namespace hippy::devtools { class DevtoolsBackendService : public std::enable_shared_from_this { public: DevtoolsBackendService(const DevtoolsConfig& devtools_config, - std::shared_ptr worker_manager); + std::shared_ptr worker_manager, std::function reconnect_handler); ~DevtoolsBackendService(); diff --git a/devtools/devtools-backend/include/tunnel/net_channel.h b/devtools/devtools-backend/include/tunnel/net_channel.h index ec84c7ba764..5e5fafbbb05 100644 --- a/devtools/devtools-backend/include/tunnel/net_channel.h +++ b/devtools/devtools-backend/include/tunnel/net_channel.h @@ -32,6 +32,7 @@ constexpr uint8_t kTaskFlag = 210; // message flag class NetChannel { public: using ReceiveDataHandler = std::function; + using ReconnectHandler = std::function; virtual ~NetChannel() {} @@ -39,7 +40,7 @@ class 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 diff --git a/devtools/devtools-backend/include/tunnel/tcp/tcp_channel.h b/devtools/devtools-backend/include/tunnel/tcp/tcp_channel.h index 835d574bb45..d791121437f 100644 --- a/devtools/devtools-backend/include/tunnel/tcp/tcp_channel.h +++ b/devtools/devtools-backend/include/tunnel/tcp/tcp_channel.h @@ -36,7 +36,7 @@ constexpr int32_t kBufferSize = 32 * 1024; class TcpChannel : public hippy::devtools::NetChannel, public std::enable_shared_from_this { 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; diff --git a/devtools/devtools-backend/include/tunnel/tunnel_service.h b/devtools/devtools-backend/include/tunnel/tunnel_service.h index 2eddaf71c2f..dae36ad761e 100644 --- a/devtools/devtools-backend/include/tunnel/tunnel_service.h +++ b/devtools/devtools-backend/include/tunnel/tunnel_service.h @@ -38,7 +38,7 @@ class TunnelService : public std::enable_shared_from_this { /** * @brief connect to frontend */ - void Connect(); + void Connect(std::function reconnect_handler); /** * @brief send data to frontend diff --git a/devtools/devtools-backend/include/tunnel/ws/web_socket_channel.h b/devtools/devtools-backend/include/tunnel/ws/web_socket_channel.h index 1da2925e013..8e244bd3b58 100644 --- a/devtools/devtools-backend/include/tunnel/ws/web_socket_channel.h +++ b/devtools/devtools-backend/include/tunnel/ws/web_socket_channel.h @@ -49,7 +49,7 @@ namespace hippy::devtools { class WebSocketChannel : public hippy::devtools::NetChannel, public std::enable_shared_from_this { 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; @@ -57,9 +57,10 @@ class WebSocketChannel : public hippy::devtools::NetChannel, public std::enable_ 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_; @@ -67,6 +68,9 @@ class WebSocketChannel : public hippy::devtools::NetChannel, public std::enable_ ReceiveDataHandler data_handler_; WSThread ws_thread_; std::vector unset_messages_{}; + bool ws_should_reconnect; + static const int MAX_RECONNECT_ATTEMPTS = 10; + int ws_reconnect_attempts; }; } // namespace hippy::devtools diff --git a/devtools/devtools-backend/src/api/devtools_backend_service.cc b/devtools/devtools-backend/src/api/devtools_backend_service.cc index d42d58a5499..61e37564fb6 100644 --- a/devtools/devtools-backend/src/api/devtools_backend_service.cc +++ b/devtools/devtools-backend/src/api/devtools_backend_service.cc @@ -29,7 +29,8 @@ namespace hippy::devtools { DevtoolsBackendService::DevtoolsBackendService(const DevtoolsConfig& devtools_config, - std::shared_ptr worker_manager) { + std::shared_ptr worker_manager, + std::function reconnect_handler) { FOOTSTONE_DLOG(INFO) << kDevToolsTag << "DevtoolsBackendService create framework:" << devtools_config.framework << ",tunnel:" << devtools_config.tunnel; auto data_provider = std::make_shared(); @@ -38,7 +39,7 @@ DevtoolsBackendService::DevtoolsBackendService(const DevtoolsConfig& devtools_co domain_dispatch_ = std::make_shared(data_channel_, worker_manager); domain_dispatch_->RegisterDefaultDomainListener(); tunnel_service_ = std::make_shared(domain_dispatch_, devtools_config); - tunnel_service_->Connect(); + tunnel_service_->Connect(reconnect_handler); notification_center->runtime_notification = std::make_shared(tunnel_service_); if (devtools_config.framework == Framework::kHippy) { diff --git a/devtools/devtools-backend/src/tunnel/tcp/tcp_channel.cc b/devtools/devtools-backend/src/tunnel/tcp/tcp_channel.cc index bf890c566b6..77fc77651a7 100644 --- a/devtools/devtools-backend/src/tunnel/tcp/tcp_channel.cc +++ b/devtools/devtools-backend/src/tunnel/tcp/tcp_channel.cc @@ -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) { diff --git a/devtools/devtools-backend/src/tunnel/tunnel_service.cc b/devtools/devtools-backend/src/tunnel/tunnel_service.cc index 67e24ce0df2..7825b142883 100644 --- a/devtools/devtools-backend/src/tunnel/tunnel_service.cc +++ b/devtools/devtools-backend/src/tunnel/tunnel_service.cc @@ -38,14 +38,14 @@ TunnelService::TunnelService(std::shared_ptr dispatch, const Dev channel_ = NetChannel::CreateChannel(devtools_config); } -void TunnelService::Connect() { +void TunnelService::Connect(std::function 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); diff --git a/devtools/devtools-backend/src/tunnel/ws/web_socket_channel.cc b/devtools/devtools-backend/src/tunnel/ws/web_socket_channel.cc index f4210113ee8..33a06355afb 100644 --- a/devtools/devtools-backend/src/tunnel/ws/web_socket_channel.cc +++ b/devtools/devtools-backend/src/tunnel/ws/web_socket_channel.cc @@ -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); @@ -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; @@ -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) @@ -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; @@ -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()) { @@ -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, @@ -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 diff --git a/devtools/devtools-integration/android/src/main/cpp/src/devtools_jni.cc b/devtools/devtools-integration/android/src/main/cpp/src/devtools_jni.cc index 5112175f9b6..024a3e4ddb8 100644 --- a/devtools/devtools-integration/android/src/main/cpp/src/devtools_jni.cc +++ b/devtools/devtools-integration/android/src/main/cpp/src/devtools_jni.cc @@ -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( + auto devtools_data_source = std::make_shared(); + 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); diff --git a/devtools/devtools-integration/native/include/devtools/devtools_data_source.h b/devtools/devtools-integration/native/include/devtools/devtools_data_source.h index be7853620f8..67a6c79819d 100644 --- a/devtools/devtools-integration/native/include/devtools/devtools_data_source.h +++ b/devtools/devtools-integration/native/include/devtools/devtools_data_source.h @@ -44,13 +44,14 @@ namespace hippy::devtools { */ class DevtoolsDataSource : public std::enable_shared_from_this { 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 worker_manager); - ~DevtoolsDataSource() = default; + void CreateDevtoolsService(const std::string& ws_url, std::shared_ptr worker_manager); /** * @brief bind dom, so that devtools can access and collect data */ @@ -110,5 +111,6 @@ class DevtoolsDataSource : public std::enable_shared_from_this hippy_dom_; uint64_t listener_id_{}; std::shared_ptr devtools_service_; + std::string context_name_; }; } // namespace hippy::devtools diff --git a/devtools/devtools-integration/native/src/devtools_data_source.cc b/devtools/devtools-integration/native/src/devtools_data_source.cc index 865d89dfb25..03b72d63ee0 100644 --- a/devtools/devtools-integration/native/src/devtools_data_source.cc +++ b/devtools/devtools-integration/native/src/devtools_data_source.cc @@ -43,7 +43,7 @@ using StringViewUtils = footstone::stringview::StringViewUtils; static std::atomic global_devtools_data_key{1}; footstone::utils::PersistentObjectMap> devtools_data_map; -DevtoolsDataSource::DevtoolsDataSource(const std::string& ws_url, +void DevtoolsDataSource::CreateDevtoolsService(const std::string& ws_url, std::shared_ptr worker_manager) { hippy::devtools::DevtoolsConfig devtools_config; devtools_config.framework = hippy::devtools::Framework::kHippy; @@ -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(devtools_config, worker_manager); + auto reconnect_handler = [WEAK_THIS] { + DEFINE_AND_CHECK_SELF(DevtoolsDataSource) + self->SetContextName(self->context_name_); + }; + devtools_service_ = std::make_shared(devtools_config, worker_manager, reconnect_handler); devtools_service_->Create(); hippy_dom_ = std::make_shared(); } @@ -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); } diff --git a/framework/ios/base/executors/HippyJSExecutor.mm b/framework/ios/base/executors/HippyJSExecutor.mm index eb8df68712d..d401c9c6cca 100644 --- a/framework/ios/base/executors/HippyJSExecutor.mm +++ b/framework/ios/base/executors/HippyJSExecutor.mm @@ -195,7 +195,8 @@ - (void)setup { NSString *wsURL = [self completeWSURLWithBridge:bridge]; if (wsURL.length > 0) { auto workerManager = std::make_shared(1); - auto devtools_data_source = std::make_shared([wsURL UTF8String], workerManager); + auto devtools_data_source = std::make_shared(); + devtools_data_source->CreateDevtoolsService([wsURL UTF8String], workerManager); self.pScope->SetDevtoolsDataSource(devtools_data_source); } }