From af729144fe2489d6b760b645ed463cbffbdac472 Mon Sep 17 00:00:00 2001 From: dijunkun Date: Mon, 7 Sep 2026 20:02:19 +0800 Subject: [PATCH] [fix] prevent stale device online status --- .../src/gui/runtime/connection_runtime.cpp | 58 +++++++++---------- .../src/gui/runtime/device_presence_cache.h | 30 ++++++++-- .../src/gui/runtime/device_presence_request.h | 43 ++++++++++++++ apps/desktop/src/gui/runtime/gui_runtime.cpp | 2 + .../src/gui/runtime/peer_event_handler.cpp | 2 + apps/desktop/src/gui/runtime/runtime_state.h | 8 ++- 6 files changed, 104 insertions(+), 39 deletions(-) create mode 100644 apps/desktop/src/gui/runtime/device_presence_request.h diff --git a/apps/desktop/src/gui/runtime/connection_runtime.cpp b/apps/desktop/src/gui/runtime/connection_runtime.cpp index d354cc29..1519d63a 100644 --- a/apps/desktop/src/gui/runtime/connection_runtime.cpp +++ b/apps/desktop/src/gui/runtime/connection_runtime.cpp @@ -14,42 +14,39 @@ #include "platform.h" #include "platform/video_renderer.h" #include "rd_log.h" +#include "runtime/device_presence_request.h" #include "runtime/gui_runtime.h" namespace crossdesk { namespace { constexpr auto kPresenceProbeTimeout = std::chrono::seconds(5); +constexpr auto kPresenceRefreshInterval = std::chrono::seconds(30); +constexpr auto kPresenceRetryInterval = std::chrono::seconds(1); } // namespace void GuiRuntime::HandleConnectionStatusChange() { - if (signal_connected_ && peer_ && need_to_send_recent_connections_) { - if (!recent_connection_ids_.empty()) { - nlohmann::json j; - j["type"] = "recent_connections_presence"; - j["user_id"] = client_id_; - j["devices"] = nlohmann::json::array(); - for (const auto& id : recent_connection_ids_) { - std::string pure_id = id; - size_t pos_y = pure_id.find('Y'); - size_t pos_n = pure_id.find('N'); - size_t pos = std::string::npos; - if (pos_y != std::string::npos && - (pos_n == std::string::npos || pos_y < pos_n)) { - pos = pos_y; - } else if (pos_n != std::string::npos) { - pos = pos_n; - } - if (pos != std::string::npos) { - pure_id = pure_id.substr(0, pos); - } - j["devices"].push_back(pure_id); - } - auto s = j.dump(); - SendSignalMessage(peer_, s.data(), s.size()); - } + if (!signal_connected_ || !peer_) { + return; + } + const auto now = std::chrono::steady_clock::now(); + if (now - last_presence_request_attempt_at_ < kPresenceRetryInterval) { + return; + } + // Consume the flag before sending so a concurrent reconnect cannot lose its + // refresh request. Keep it pending while disconnected or after a send error. + const bool requested = need_to_send_recent_connections_.exchange(false); + if (!requested && now < next_presence_refresh_at_) { + return; + } + last_presence_request_attempt_at_ = now; + const auto message = + BuildDevicePresenceRequest(client_id_, recent_connection_ids_, true).dump(); + if (SendSignalMessage(peer_, message.data(), message.size()) == 0) { + next_presence_refresh_at_ = now + kPresenceRefreshInterval; + } else { + need_to_send_recent_connections_ = true; } - need_to_send_recent_connections_ = false; } void GuiRuntime::HandlePendingPresenceProbe() { @@ -200,11 +197,9 @@ int GuiRuntime::RequestSingleDevicePresence(const std::string& remote_id, pending_presence_remember_password_ = remember_password; } - nlohmann::json j; - j["type"] = "recent_connections_presence"; - j["user_id"] = client_id_; - j["devices"] = nlohmann::json::array({remote_id}); - auto s = j.dump(); + const auto s = BuildDevicePresenceRequest( + client_id_, recent_connection_ids_, false, remote_id) + .dump(); int ret = SendSignalMessage(peer_, s.data(), s.size()); if (ret != 0) { @@ -308,6 +303,7 @@ void GuiRuntime::CloseConnectionPeer() { devices_.StopSpeakerCapturer(); devices_.StopKeyboardCapturer(); signal_connected_ = false; + device_presence_cache_.SetSignalConnected(false); signal_status_ = SignalStatus::SignalClosed; PeerPtr* peer; { diff --git a/apps/desktop/src/gui/runtime/device_presence_cache.h b/apps/desktop/src/gui/runtime/device_presence_cache.h index e799f29f..ad4103f6 100644 --- a/apps/desktop/src/gui/runtime/device_presence_cache.h +++ b/apps/desktop/src/gui/runtime/device_presence_cache.h @@ -7,6 +7,7 @@ #ifndef _DEVICE_PRESENCE_CACHE_H_ #define _DEVICE_PRESENCE_CACHE_H_ +#include #include #include #include @@ -15,15 +16,29 @@ namespace crossdesk { class DevicePresenceCache { public: - void SetOnline(const std::string &device_id, bool online) { + using Clock = std::chrono::steady_clock; + static constexpr auto kMaxAge = std::chrono::seconds(60); + + void SetSignalConnected(bool connected) { std::lock_guard lock(mutex_); - cache_[device_id] = online; + signal_connected_ = connected; + cache_.clear(); } - bool IsOnline(const std::string &device_id) const { + void SetOnline(const std::string &device_id, bool online, + Clock::time_point now = Clock::now()) { + std::lock_guard lock(mutex_); + if (signal_connected_) { + cache_[device_id] = {online, now}; + } + } + + bool IsOnline(const std::string &device_id, + Clock::time_point now = Clock::now()) const { std::lock_guard lock(mutex_); const auto it = cache_.find(device_id); - return it != cache_.end() && it->second; + return signal_connected_ && it != cache_.end() && it->second.online && + now - it->second.updated_at < kMaxAge; } void Clear() { @@ -32,7 +47,12 @@ public: } private: - std::unordered_map cache_; + struct Entry { + bool online; + Clock::time_point updated_at; + }; + bool signal_connected_ = false; + std::unordered_map cache_; mutable std::mutex mutex_; }; diff --git a/apps/desktop/src/gui/runtime/device_presence_request.h b/apps/desktop/src/gui/runtime/device_presence_request.h new file mode 100644 index 00000000..2f0e2092 --- /dev/null +++ b/apps/desktop/src/gui/runtime/device_presence_request.h @@ -0,0 +1,43 @@ +/* + * @Author: DI JUNKUN + * @Date: 2026-09-07 + * Copyright (c) 2026 by DI JUNKUN, All Rights Reserved. + */ + +#ifndef _DEVICE_PRESENCE_REQUEST_H_ +#define _DEVICE_PRESENCE_REQUEST_H_ + +#include +#include +#include +#include + +namespace crossdesk { + +inline nlohmann::json BuildDevicePresenceRequest( + const std::string& client_id, const std::vector& recent_ids, + bool subscribe, const std::string& probe_id = {}) { + std::vector devices; + for (const auto& id : recent_ids) { + // Recent connection keys contain Y/N plus display settings after the ID. + const std::string pure_id = id.substr(0, id.find_first_of("YN")); + if (!pure_id.empty() && + std::find(devices.begin(), devices.end(), pure_id) == devices.end()) { + devices.push_back(pure_id); + } + } + if (!probe_id.empty() && + std::find(devices.begin(), devices.end(), probe_id) == devices.end()) { + devices.push_back(probe_id); + } + // Include recent devices even in a one-shot probe: older servers ignore + // subscribe=false and replace their subscriptions with this device list. + return {{"type", "recent_connections_presence"}, + {"user_id", client_id}, + {"devices", devices}, + {"subscribe", subscribe}}; +} + +} // namespace crossdesk + +#endif \ No newline at end of file diff --git a/apps/desktop/src/gui/runtime/gui_runtime.cpp b/apps/desktop/src/gui/runtime/gui_runtime.cpp index 98399500..4973c78c 100644 --- a/apps/desktop/src/gui/runtime/gui_runtime.cpp +++ b/apps/desktop/src/gui/runtime/gui_runtime.cpp @@ -200,6 +200,8 @@ int GuiRuntime::CreateConnectionPeer() { // The previous peer may have left a terminal status behind. Reset it before // Init() starts emitting callbacks for the newly selected server. signal_connected_ = false; + device_presence_cache_.SetSignalConnected(false); + need_to_send_recent_connections_ = true; signal_status_ = SignalStatus::SignalConnecting; { diff --git a/apps/desktop/src/gui/runtime/peer_event_handler.cpp b/apps/desktop/src/gui/runtime/peer_event_handler.cpp index af9f92b3..67628055 100644 --- a/apps/desktop/src/gui/runtime/peer_event_handler.cpp +++ b/apps/desktop/src/gui/runtime/peer_event_handler.cpp @@ -167,6 +167,8 @@ void PeerEventHandler::OnSignalStatus(SignalStatus status, const char* user_id, std::string client_id(user_id, user_id_size); if (client_id == runtime->client_id_) { runtime->signal_status_ = status; + runtime->device_presence_cache_.SetSignalConnected( + status == SignalStatus::SignalConnected); if (SignalStatus::SignalConnecting == status) { runtime->signal_connected_ = false; } else if (SignalStatus::SignalConnected == status) { diff --git a/apps/desktop/src/gui/runtime/runtime_state.h b/apps/desktop/src/gui/runtime/runtime_state.h index 5c435db3..c4fca9f2 100644 --- a/apps/desktop/src/gui/runtime/runtime_state.h +++ b/apps/desktop/src/gui/runtime/runtime_state.h @@ -55,13 +55,15 @@ struct RecentConnectionsState { int recent_connection_image_height_ = 90; uint32_t recent_connection_image_save_time_ = 0; DevicePresenceCache device_presence_cache_; - bool need_to_send_recent_connections_ = true; + std::atomic need_to_send_recent_connections_{true}; + std::chrono::steady_clock::time_point next_presence_refresh_at_{}; + std::chrono::steady_clock::time_point last_presence_request_attempt_at_{}; }; struct PeerState { SignalStatus signal_status_ = SignalStatus::SignalClosed; std::string signal_status_str_; - bool signal_connected_ = false; + std::atomic signal_connected_{false}; PeerPtr *peer_ = nullptr; PeerPtr *peer_reserved_ = nullptr; std::string video_primary_label_ = "primary_display"; @@ -227,4 +229,4 @@ struct RuntimeState : InfrastructureState, } // namespace crossdesk::gui_detail -#endif \ No newline at end of file +#endif