From b577a2a98398aa0cabe9c1b30bb04a8243269634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cyrille=20B=C3=A9raud?= Date: Tue, 1 Sep 2026 07:16:36 -0400 Subject: [PATCH] proxy_client: send push subscriptions deferred at token time setPushNotificationToken() stores the token and returns early when the proxy connection is not established yet, without ever scheduling the subscriptions it skipped. Nothing retries them afterwards: restartListeners() only resubscribes listeners whose opstate is not ok, so listeners that are healthy keep the subscription they were created with and the server is never told the session id of the current run. Clients set the token while the account starts, before the first proxy reply arrives, so this path is the common one rather than a corner case. The server then keeps the listener registered under the session of a previous run and stamps every notification with that stale id. The client compares it with pushSessionId_, which is drawn once per DhtProxyClient, and discards the notification: if (sessionId != notification.end() and sessionId->second != pushSessionId_) return PushNotificationResult::IgnoredWrongSession; Observed on an Android device: every push received was rejected with IgnoredWrongSession, 83 out of 83, including calls and the diagnostic ping. Restarting the proxy instance cleared the stale listeners, after which the device received no push at all, confirming it never subscribes for push with its current session. Remember the skipped subscriptions and send them from restartListeners() once the connection comes up. searchLock_ is already held there, so the loop is inlined rather than calling resubscribeAll(), and lockCurrentProxyInfos_ is released before searchLock_ is taken in setPushNotificationToken() to keep the lock order consistent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- include/opendht/dht_proxy_client.h | 15 +++++++++++ src/dht_proxy_client.cpp | 43 +++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/include/opendht/dht_proxy_client.h b/include/opendht/dht_proxy_client.h index ec4d2395..3fc5d01b 100644 --- a/include/opendht/dht_proxy_client.h +++ b/include/opendht/dht_proxy_client.h @@ -368,6 +368,14 @@ private: std::string pushClientId_; std::string pushSessionId_; + /** + * Set when setPushNotificationToken() could not send the subscriptions + * because the proxy connection was not established yet. Consumed by + * restartListeners() once the connection comes up. + * Guarded by lockCurrentProxyInfos_. + */ + bool pushSubscriptionPending_ {false}; + mutable std::mutex lockCurrentProxyInfos_; NodeStatus statusIpv4_ {NodeStatus::Disconnected}; NodeStatus statusIpv6_ {NodeStatus::Disconnected}; @@ -440,6 +448,13 @@ private: */ void resubscribe(const InfoHash& key, const size_t token, Listener& listener); +#ifdef OPENDHT_PUSH_NOTIFICATIONS + /** + * Resubscribe every listener so the server learns the current push session. + */ + void resubscribeAll(); +#endif + /** * If we want to use push notifications by default. * NOTE: empty by default to avoid to use services like FCM or APN. diff --git a/src/dht_proxy_client.cpp b/src/dht_proxy_client.cpp index 96e4d414..226c6fa6 100644 --- a/src/dht_proxy_client.cpp +++ b/src/dht_proxy_client.cpp @@ -1205,6 +1205,25 @@ DhtProxyClient::restartListeners(const asio::error_code& ec) } } if (not deviceKey_.empty()) { + bool pending = false; + { + std::lock_guard l(lockCurrentProxyInfos_); + pending = pushSubscriptionPending_; + pushSubscriptionPending_ = false; + } + if (pending) { + // setPushNotificationToken() could not send the subscriptions + // because the proxy connection was not established yet. Send them + // now, so the server learns the session id of this run: healthy + // listeners are skipped by the loop below, and would otherwise + // never be subscribed for push at all. + if (logger_) + logger_->debug("[proxy:client] [listeners] sending deferred push subscriptions"); + for (auto& search : searches_) + for (auto& listener : search.second.listeners) + resubscribe(search.first, listener.first, listener.second); + return; + } if (logger_) logger_->debug("[proxy:client] [listeners] resubscribe due to a connectivity change"); // Connectivity changed, refresh all subscribe @@ -1427,18 +1446,36 @@ DhtProxyClient::setPushNotificationToken([[maybe_unused]] const std::string& tok if (deviceKey_ == token) return; deviceKey_ = token; - if (!(statusIpv4_ == NodeStatus::Connected || statusIpv6_ == NodeStatus::Connected)) + if (!(statusIpv4_ == NodeStatus::Connected || statusIpv6_ == NodeStatus::Connected)) { + // The proxy connection is not up yet, so the subscriptions can't be + // sent now. Remember that they are owed: clients typically set the + // token while the account starts, before the first proxy reply has + // been received, and nothing else would ever push the current + // session id to the server. The server would then keep notifying + // this device with the session of a previous run, and every push + // would be discarded as PushNotificationResult::IgnoredWrongSession. + pushSubscriptionPending_ = true; return; + } + pushSubscriptionPending_ = false; } + resubscribeAll(); +#endif +} + +#ifdef OPENDHT_PUSH_NOTIFICATIONS +void +DhtProxyClient::resubscribeAll() +{ if (logger_) - logger_->debug("[proxy:client] [push] token changed, resubscribing"); + logger_->debug("[proxy:client] [push] resubscribing every listener"); std::lock_guard lock(searchLock_); for (auto& search : searches_) { for (auto& listener : search.second.listeners) { resubscribe(search.first, listener.first, listener.second); } } -#endif } +#endif } // namespace dht