diff --git a/waku/v2/api/filter/filter.go b/waku/v2/api/filter/filter.go index 6b6f3f46..6f2f008b 100644 --- a/waku/v2/api/filter/filter.go +++ b/waku/v2/api/filter/filter.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "sync" + "sync/atomic" "time" "github.com/google/uuid" @@ -57,6 +58,9 @@ type Sub struct { resubscribeInProgress bool id string errcnt int + // backgroundMode suppresses subscription renewal when the app UI is not visible. + // Toggled via SetBackgroundMode; read from subscriptionLoop goroutine. + backgroundMode atomic.Bool // rateLimitedUntil is set when subscribe() observes a *SubscribeError whose // FailedPeers contain at least one HTTP 429. While time.Now().Before(rateLimitedUntil), // subscriptionLoop suppresses retry triggers (ticker push and checkAndResubscribe). @@ -139,6 +143,11 @@ func (apiSub *Sub) subscriptionLoop(loopInterval time.Duration) { select { case <-ticker.C: apiSub.errcnt = 0 //reset errorCount + if apiSub.backgroundMode.Load() { + // In background: skip health check to avoid waking the LTE radio. + // SetBackgroundMode(false) triggers resubscription on foreground. + continue + } if shouldHonourRateLimitBackoff(apiSub.rateLimitedUntil, time.Now()) { apiSub.log.Debug("ticker push suppressed by rate-limit backoff", zap.Time("rate-limited-until", apiSub.rateLimitedUntil), @@ -154,6 +163,14 @@ func (apiSub *Sub) subscriptionLoop(loopInterval time.Duration) { apiSub.cleanup() return case subId := <-apiSub.closing: + if apiSub.backgroundMode.Load() { + // In background: subscription expired but don't resubscribe now. + // SetBackgroundMode(false) will trigger resubscription on foreground. + apiSub.log.Debug("resubscribe suppressed: app in background", + zap.String("sub-id", subId), + ) + continue + } if shouldHonourRateLimitBackoff(apiSub.rateLimitedUntil, time.Now()) { apiSub.log.Debug("checkAndResubscribe suppressed by rate-limit backoff", zap.Time("rate-limited-until", apiSub.rateLimitedUntil), @@ -174,6 +191,23 @@ func (apiSub *Sub) subscriptionLoop(loopInterval time.Duration) { } } +// SetBackgroundMode controls whether this subscription suppresses renewal. +// Call with background=true when the app UI is not visible (screen locked). +// Call with background=false when returning to foreground; this triggers an +// immediate resubscription attempt for any subscriptions that expired while +// backgrounded. +func (apiSub *Sub) SetBackgroundMode(background bool) { + apiSub.backgroundMode.Store(background) + if !background { + // Returning to foreground: prod the loop to resubscribe. + select { + case apiSub.closing <- "": + default: + // A resubscription is already queued. + } + } +} + func (apiSub *Sub) checkAndResubscribe(subId string) { var failedPeer peer.ID if subId != "" { diff --git a/waku/v2/api/filter/filter_manager.go b/waku/v2/api/filter/filter_manager.go index c5d47872..5f2c2589 100644 --- a/waku/v2/api/filter/filter_manager.go +++ b/waku/v2/api/filter/filter_manager.go @@ -47,9 +47,9 @@ type FilterManager struct { // a deadlock where SubscribeFilter would block on a full channel while still // holding mgr.Lock(), preventing the only drainer (checkAndProcessQueue, also // invoked under the same lock) from running. - waitingToSubQueue []filterConfig - envProcessor EnevelopeProcessor - networkConnType byte + waitingToSubQueue []filterConfig + envProcessor EnevelopeProcessor + networkConnType byte } type SubDetails struct { @@ -189,6 +189,19 @@ func (mgr *FilterManager) NetworkChange() { mgr.node.PingPeers() // ping all peers to check if subscriptions are alive } +// SetBackgroundMode notifies all active subscriptions of the app's visibility +// state. When background=true, subscriptions suppress renewal keepalives to +// avoid waking the LTE radio while the screen is locked. When background=false +// (returning to foreground), each subscription immediately attempts to +// resubscribe if its filter has expired. +func (mgr *FilterManager) SetBackgroundMode(background bool) { + mgr.Lock() + defer mgr.Unlock() + for _, subDetails := range mgr.filterSubscriptions { + subDetails.sub.SetBackgroundMode(background) + } +} + // checkAndProcessQueue drains the offline-pending filter queue. For each batch // that matches the given pubsubTopic (or always, when pubsubTopic == ""), a // subscribe goroutine is spawned; non-matching batches are retained for a