fix(messenger): defer mailserver history sync when app is in background#7508
fix(messenger): defer mailserver history sync when app is in background#7508xAlisher wants to merge 6 commits into
Conversation
Adds a backgroundMode flag to Messenger that gates automatic calls to
asyncRequestAllHistoricMessages(). Two trigger sites are guarded:
- handleConnectionChange (messenger.go): fires when the Waku light
client detects a connectivity change (e.g. returning from airplane
mode). Previously triggered a full history sync unconditionally,
even with the screen locked.
- checkForStorenodeCycleSignals (messenger_mailserver_cycle.go): fires
when a storenode becomes available. Same unconditional trigger.
When backgroundMode=true both sites skip the sync and return. When
SetAppBackground(false) is called (app returns to foreground) the deferred
sync runs immediately.
Unlike SetPaused(), this does not pause Waku transport or data-sync —
Waku keeps running so push notifications continue to work.
A new SetAppBackground(bool) RPC is exposed via services/ext/api.go.
The Android layer (StatusGoService.java) should call it from
scheduleBackendLifecycleUpdate() alongside PauseServices/ResumeServices.
Measured impact (status-im/status-app#21045):
- Post-reconnect background sync storm: ~425 mAh/hr → ~0 mAh/hr
- Messages fetched on next foreground open instead
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wires the new SetAppBackground RPC (status-im/status-go#7508) into scheduleBackendLifecycleUpdate so the messenger layer knows when the app is backgrounded and can defer mailserver history syncs accordingly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Jenkins BuildsClick to see older builds (17)
|
jrainville
left a comment
There was a problem hiding this comment.
Looks good for a first step to address the problem.
The one thing that I think can be changed is that Messenger already has ToBackground and ToForeground. You can set backgroundMode in those instead of adding a new API.
In future releases, we could improve the solution to fetch only the last ~15 minutes instead of all messages.
Or make it smart and detect when was the last time we were online.
R1 of battery-drain fix (#21045).
When the Android app is backgrounded, expiring light-client filter
subscriptions triggered wf.Subscribe() RPCs that woke the LTE radio.
This adds a background-mode gate at every layer of the call chain:
Messenger.SetAppBackground
→ messaging.API.SetFilterBackgroundMode
→ transport.Transport.SetFilterBackgroundMode
→ Waku interface / gowaku.Waku
→ filterapi.FilterManager.SetBackgroundMode (go-waku)
→ filterapi.Sub.SetBackgroundMode (go-waku)
While backgrounded the subscriptionLoop's ticker and closing-channel
cases are suppressed; on foreground return a single resubscribe is
queued to catch any subscriptions that expired while idle.
go-waku changes: logos-messaging/logos-delivery-go#1304
go.mod replace directive: xAlisher/go-waku@ab4609ef
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
go mod tidy drops the original waku-org/go-waku checksum entries once the replace directive points to xAlisher/go-waku. Sync go.sum to match. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
❌ Your patch check has failed because the patch coverage (12.50%) is below the target coverage (50.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## develop #7508 +/- ##
===========================================
- Coverage 61.53% 61.49% -0.04%
===========================================
Files 851 851
Lines 119255 119285 +30
===========================================
- Hits 73380 73360 -20
- Misses 38343 38423 +80
+ Partials 7532 7502 -30
Flags with carried forward coverage won't be shown. Click here to find out more.
|
…sion) Adds FilterHealthCheckLoop background guard — the dominant LTE radio wakeup source (1-min pings to filter peers, batched by Doze into ~15-min windows at ~55% duty cycle). go-waku change: logos-messaging/logos-delivery-go#1304 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| // | ||
| // Unlike SetPaused, this does NOT pause Waku transport or data-sync — Waku | ||
| // continues running so push notifications keep working. | ||
| func (m *Messenger) SetAppBackground(background bool) { |
There was a problem hiding this comment.
@xAlisher just a few lines above we already have ToForeground()/ToBackground() functions. ToForeground already calls asyncRequestAllHistoricMessages() and as I see you're now adding a second, parallel lifecycle pair (via SetAppBackground). Why not reuse?
There was a problem hiding this comment.
Also we should think if shouldSync() should be called when doing m.asyncRequestAllHistoricMessages() in ToForeground().
|
|
||
| // Temporary: use fork with background-mode filter subscription fix until | ||
| // logos-messaging/logos-delivery-go#1304 is merged and released. | ||
| replace github.com/waku-org/go-waku => github.com/xAlisher/go-waku v0.10.2-0.20260603060940-9f8361930df9 |
There was a problem hiding this comment.
@xAlisher here and in other places we should refer to github.com/waku-org/go-waku not to the personal fork.
Remove SetAppBackground() as a standalone API. Instead, set backgroundMode and gate filter health-check pings directly inside the existing ToBackground()/ToForeground() lifecycle hooks, which are already the canonical entry points for app visibility changes. Also fix a stale PR URL in the go.mod comment (logos-messaging → waku-org). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Thanks @jrainville and @saledjenic — good call. Updated in 26b2e81:
Re Re |
Add PausableMessenger wrapper (parallel to PausableMediaServer) that delegates Pause()/Resume() to Messenger.ToBackground()/ToForeground(). Register it in populateServiceRegistry() so that the existing PauseServices/ResumeServices mobile API automatically gates filter health-check pings and mailserver history syncs when the app is backgrounded — without a separate SetAppBackground API call. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jrainville
left a comment
There was a problem hiding this comment.
Looks good. I just want to double check that we don't call ToBackground/ToForeground twice.
| func (p *PausableMessenger) PausableName() string { return "messenger" } | ||
|
|
||
| func (p *PausableMessenger) Pause() error { | ||
| p.m.ToBackground() |
There was a problem hiding this comment.
Was the Messenger's ToBackground never called before?
There was a problem hiding this comment.
Pull request overview
This PR reduces unnecessary network activity (and associated battery drain) while the app is backgrounded by gating (1) mailserver history sync triggers and (2) Waku light-client filter maintenance work behind an app background/foreground signal, and wiring that signal through the messaging stack and service pause/resume flow.
Changes:
- Add a
backgroundModegate inprotocol.Messengerto defer mailserver history sync until returning to foreground. - Introduce
SetFilterBackgroundMode(bool)plumbing fromMessenger → messaging.API → Transport → Wakuto suppress filter renewal/maintenance while backgrounded. - Register a pausable messenger wrapper so
PauseServices/ResumeServicesdrivesMessenger.ToBackground/ToForeground.
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| services/ext/api.go | Adds an import (currently unused) in ext public API layer. |
| protocol/messenger.go | Tracks background state; gates history sync + cascades filter background mode. |
| protocol/messenger_mailserver_cycle.go | Skips history sync trigger when storenode becomes available if backgrounded. |
| pkg/messaging/waku/types/waku.go | Extends Waku interface with SetFilterBackgroundMode. |
| pkg/messaging/waku/gowaku.go | Implements SetFilterBackgroundMode by delegating to filter manager. |
| pkg/messaging/layers/transport/transport.go | Adds transport-level pass-through to Waku background mode. |
| pkg/messaging/api.go | Exposes messaging API method to set filter background mode. |
| pkg/backend/node/service_registry.go | Adds PausableMessenger wrapper to map pause/resume to background/foreground. |
| pkg/backend/node/get_status_node.go | Registers the pausable messenger in the service registry. |
| go.mod | Temporarily replaces github.com/waku-org/go-waku with a fork containing background-mode fixes. |
| go.sum | Updates sums to match the go-waku fork replacement. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import ( | ||
| "context" | ||
| "errors" | ||
| "time" |
| } | ||
|
|
||
| // PausableMessenger wraps a protocol.Messenger to implement common.Pausable. | ||
| // Pause() → ToBackground() and Resume() → ToForeground() so that the Java |
| func (p *PausableMessenger) Pause() error { | ||
| p.m.ToBackground() |
There was a problem hiding this comment.
It's not ideal that Pause == ToBackground here.
I understand that Status app calls Pause when moving to background. But at this API level it's not clear. If I'm calling messenger.Pause, I expect it to pause completely, not just switch some functionality to "background mode".
Not sure how to simply workaround it with current APIs though.
| // Skip history sync when backgrounded; ToForeground() | ||
| // will trigger it when the app returns to foreground. | ||
| if !m.backgroundMode.Load() { |
There was a problem hiding this comment.
Instead of skipping store node requests, why don't we just completely disconnect from storenodes and stop the StorenodeCycle?
|
Closing in favour of #7516 by @jrainville, which is a cleaner release-based version of this PR with the go-waku fork dependency removed. For reference, the measurement data that motivated this work:
The dominant drain source on cellular was the What lands in 2.38 via #7516: mailserver history sync deferred when backgrounded + Messenger registered as a Pausable service, which prevents the reconnect sync-storm on foreground return. |
Problem
When the app is backgrounded, two separate subsystems drive spurious network activity that drains the battery:
R2 — Mailserver history sync (this PR, commit 1): `asyncRequestAllHistoricMessages()` fires unconditionally from two sites when connectivity resumes or a storenode becomes available — even with the screen locked.
R1 — Waku filter-subscription renewal (this PR, commit 2): Light-client filter subscriptions have a ~13.5 min relay-side TTL. When one expires, `subDetails.Closing` fires → `wf.Subscribe()` RPC → LTE radio wakeup. The 5s health-check ticker in `subscriptionLoop` also triggers proactive resubscribes. Both paths run in the background service process regardless of screen state.
R1b — Filter health-check pings (this PR, commit 4): `FilterHealthCheckLoop` fires every 1 minute and sends a Waku protocol ping to each subscribed filter peer. On Android, these pings queue up during Doze and flush in maintenance windows (~15 min), keeping the LTE modem at ~55–58% duty cycle. This was the dominant radio wakeup source — confirmed by mag's measurement on #21111 (modem still 58% active after R1 subscription-renewal fix alone).
Measured impact (status-im/status-app#21045, T5 soak): 321 MB RX in 30 min, LTE radio 99% duty cycle, ~425 mAh/hr peak drain.
Fix
R2 — Defer mailserver sync (commit 1: d793ad5)
Adds `backgroundMode atomic.Bool` to `Messenger`. Two `asyncRequestAllHistoricMessages()` call sites are guarded; sync is deferred until `SetAppBackground(false)`. New `SetAppBackground(bool)` RPC exposed via `services/ext/api.go`.
R1 — Suppress filter renewal (commit 2: 9aac52a)
Wires background-mode gate through every layer of the call chain:
While backgrounded, the `subscriptionLoop` ticker and closing-channel cases skip resubscription. On foreground return, a single resubscribe is queued to catch any subscriptions that expired while idle.
R1b — Suppress filter health-check pings (commit 4: 72956f2)
Adds `backgroundMode atomic.Bool` to `WakuFilterLightNode` (protocol/filter layer) with `SetBackgroundMode(bool)`. `FilterHealthCheckLoop` skips `PingPeers()` when backgrounded. `FilterManager.SetBackgroundMode` now cascades to both layers:
go-waku changes: logos-messaging/logos-delivery-go#1304
go.mod `replace` directive: `xAlisher/go-waku@9f836193` (temporary, until #1304 merges)
Companion PR
status-im/status-app#21111
Test plan
Closes status-im/status-app#21045 (R1 + R1b + R2)