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>
This commit is contained in:
Cyrille BéraudandCopilot committed 2026-09-01 07:16:36 -04:00
1 parent d676aef027
commit b577a2a983
2 files changed
+55 -3

No files matched your search

+15
View File
@@ -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.
+40 -3
View File
@@ -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