Skip to content

fix(messenger): defer mailserver history sync when app is in background#7508

Closed
xAlisher wants to merge 6 commits into
developfrom
fix/defer-mailserver-sync-in-background
Closed

fix(messenger): defer mailserver history sync when app is in background#7508
xAlisher wants to merge 6 commits into
developfrom
fix/defer-mailserver-sync-in-background

Conversation

@xAlisher

@xAlisher xAlisher commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

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:

Messenger.SetAppBackground
  → messaging.API.SetFilterBackgroundMode
  → transport.Transport.SetFilterBackgroundMode
  → Waku interface / gowaku.Waku.SetFilterBackgroundMode
  → filterapi.FilterManager.SetBackgroundMode   (go-waku)
  → filterapi.Sub.SetBackgroundMode             (go-waku)

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:

filterapi.FilterManager.SetBackgroundMode
  → filterapi.Sub.SetBackgroundMode             (subscription renewal)
  → WakuFilterLightNode.SetBackgroundMode       (health-check pings)  ← dominant source

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

  • Background → airplane mode off → no `asyncRequestAllHistoricMessages` in logcat
  • Background → filter subscription TTL expires → no `wf.Subscribe()` RPC in logcat
  • Background → 1 min passes → no `PingPeers` / filter ping in logcat
  • LTE modem duty cycle drops from ~58% to near 0% after Doze engages
  • Foreground → sync runs, messages arrive, filter subscriptions renew, pings resume
  • Push notifications unaffected (Waku service continues running)

Closes status-im/status-app#21045 (R1 + R1b + R2)

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>
xAlisher added a commit to status-im/status-app that referenced this pull request Jun 2, 2026
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>
@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

⚠️ Companion PR Needs Update

#21111 is not using the latest status-go commit (5ab25f59a746da0c4b6cbbb6be66f997473b7a9d).

Update vendor/status-go in the companion PR.

@status-im-auto

status-im-auto commented Jun 2, 2026

Copy link
Copy Markdown
Member

Jenkins Builds

Click to see older builds (17)
Commit #️⃣ Finished (UTC) Duration Platform Result
✔️ d793ad5 1 2026-06-02 15:55:29 ~5 min linux/status-go 📦zip
✔️ d793ad5 1 2026-06-02 15:57:15 ~6 min macos/status-go 📦zip
✔️ d793ad5 1 2026-06-02 16:01:48 ~11 min windows/status-go 📦zip
✖️ d793ad5 1 2026-06-02 16:11:08 ~20 min tests-rpc 📄log
✔️ d793ad5 1 2026-06-02 16:27:56 ~37 min tests 📄log
✔️ 9aac52a 2 2026-06-02 16:14:44 ~4 min macos/status-go 📦zip
✔️ 9aac52a 2 2026-06-02 16:21:58 ~11 min linux/status-go 📦zip
✔️ 9aac52a 2 2026-06-02 16:23:30 ~13 min windows/status-go 📦zip
✔️ 9aac52a 2 2026-06-02 16:43:35 ~32 min tests-rpc 📄log
✔️ ee0cd6c 3 2026-06-02 16:28:30 ~4 min macos/status-go 📦zip
✔️ ee0cd6c 3 2026-06-02 16:29:20 ~5 min windows/status-go 📦zip
✔️ ee0cd6c 3 2026-06-02 16:29:37 ~5 min linux/status-go 📦zip
✔️ ee0cd6c 3 2026-06-02 17:01:52 ~18 min tests-rpc 📄log
✔️ ee0cd6c 2 2026-06-02 17:04:01 ~35 min tests 📄log
✔️ 72956f2 4 2026-06-03 06:15:32 ~4 min macos/status-go 📦zip
✔️ 72956f2 4 2026-06-03 06:16:36 ~5 min windows/status-go 📦zip
✔️ 72956f2 3 2026-06-03 08:19:13 ~2 hr 8 min tests 📄log
Commit #️⃣ Finished (UTC) Duration Platform Result
26b2e81 5 2026-06-03 09:20:38 ~2 min macos/status-go 📄log
26b2e81 5 2026-06-03 09:20:50 ~3 min linux/status-go 📄log
26b2e81 5 2026-06-03 09:23:32 ~5 min windows/status-go 📄log
5ab25f5 6 2026-06-03 09:42:41 ~2 min linux/status-go 📄log
5ab25f5 6 2026-06-03 09:43:50 ~3 min windows/status-go 📄log
5ab25f5 6 2026-06-03 09:45:12 ~5 min macos/status-go 📄log
✖️ 5ab25f5 6 2026-06-03 10:12:27 ~3 min tests-rpc 📄log
✖️ 5ab25f5 5 2026-06-03 10:12:40 ~2 min tests 📄log

@jrainville jrainville left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

xAlisher and others added 2 commits June 2, 2026 18:09
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

codecov Bot commented Jun 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 12.50000% with 28 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.49%. Comparing base (db8f71a) to head (ee0cd6c).

Files with missing lines Patch % Lines
protocol/messenger.go 10.00% 9 Missing ⚠️
services/ext/api.go 0.00% 6 Missing ⚠️
pkg/messaging/waku/gowaku.go 0.00% 5 Missing ⚠️
pkg/messaging/api.go 0.00% 4 Missing ⚠️
pkg/messaging/layers/transport/transport.go 0.00% 4 Missing ⚠️

❌ 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     
Flag Coverage Δ
functional 40.74% <12.50%> (-0.02%) ⬇️
unit 55.26% <3.12%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
pkg/messaging/waku/types/waku.go 86.20% <ø> (ø)
protocol/messenger_mailserver_cycle.go 73.77% <100.00%> (+0.43%) ⬆️
pkg/messaging/api.go 41.02% <0.00%> (-4.69%) ⬇️
pkg/messaging/layers/transport/transport.go 61.63% <0.00%> (-0.90%) ⬇️
pkg/messaging/waku/gowaku.go 63.39% <0.00%> (-0.55%) ⬇️
services/ext/api.go 40.52% <0.00%> (-0.36%) ⬇️
protocol/messenger.go 61.57% <10.00%> (-0.16%) ⬇️

... and 30 files with indirect coverage changes

…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>
Comment thread protocol/messenger.go Outdated
//
// 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) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also we should think if shouldSync() should be called when doing m.asyncRequestAllHistoricMessages() in ToForeground().

Comment thread go.mod

// 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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>
@xAlisher

xAlisher commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @jrainville and @saledjenic — good call. Updated in 26b2e81:

  • Removed SetAppBackground() entirely; folded backgroundMode.Store() + SetFilterBackgroundMode() into the existing ToBackground()/ToForeground() hooks
  • Fixed the stale PR URL in the go.mod comment (was pointing to logos-messaging, corrected to waku-org/go-waku#1304)

Re go.mod fork reference — that will self-resolve once logos-messaging/logos-delivery-go#1304 merges; the replace directive is explicitly marked temporary.

Re shouldSync() in ToForeground()ToForeground() already skips if mailserver history was recently fetched (the asyncRequestAllHistoricMessages impl has its own guard). Happy to add an explicit check there if you think it's worth it, just let me know.

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 jrainville left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was the Messenger's ToBackground never called before?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems so

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 backgroundMode gate in protocol.Messenger to defer mailserver history sync until returning to foreground.
  • Introduce SetFilterBackgroundMode(bool) plumbing from Messenger → messaging.API → Transport → Waku to suppress filter renewal/maintenance while backgrounded.
  • Register a pausable messenger wrapper so PauseServices/ResumeServices drives Messenger.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.

Comment thread services/ext/api.go
Comment on lines 3 to 6
import (
"context"
"errors"
"time"
Comment thread pkg/backend/node/service_registry.go
}

// PausableMessenger wraps a protocol.Messenger to implement common.Pausable.
// Pause() → ToBackground() and Resume() → ToForeground() so that the Java

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Java? 👀

Comment on lines +59 to +60
func (p *PausableMessenger) Pause() error {
p.m.ToBackground()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +162 to +164
// Skip history sync when backgrounded; ToForeground()
// will trigger it when the app returns to foreground.
if !m.backgroundMode.Load() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of skipping store node requests, why don't we just completely disconnect from storenodes and stop the StorenodeCycle?

@xAlisher

xAlisher commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

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:

Scenario Modem duty cycle Drain
T3 — WiFi, bare account (baseline) 1.6 mAh/hr
T4 — Cellular, bare account 55% 97.5 mAh/hr
T5b — Cellular, loaded account, steady-state 55% 144 mAh/hr
T5b — Cellular, loaded account, with this fix ~15% ~40 mAh/hr

The dominant drain source on cellular was the FilterHealthCheckLoop ping interval (R1b / go-waku side). That part is deferred to 2.39 / Logos Delivery integration — tracked in logos-messaging/logos-delivery-go#1304.

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.

@xAlisher xAlisher closed this Jun 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Mobile] Android flagged Status for high battery consumption and CPU usage

6 participants