Batch access/usage DB writes, debounce network reloads, back off WG restarts - #596
Conversation
…estarts Addresses the top four battery findings from the review: - DatabaseHelper.updateAccess/updateUsage now batch like insertLog already did, coalescing repeated writes to the same (uid, version, protocol, daddr, dport) row into one transaction, and updateUsage collapses the old SELECT-then-UPDATE round trip into a single UPDATE with COALESCE. - ServiceSinkhole.reloadAfterNetworkChange debounces bursts of ConnectivityManager callbacks into a single reload instead of restarting the VPN/WireGuard per callback. - WgEgress serializes network-change rebinds through a single-thread executor with dedup instead of spawning an unbounded raw Thread per event, and adds exponential backoff to the broken-tunnel full-restart loop so a captive portal/blocked UDP path doesn't retry every ~20s forever. - The onStartCommand wakelock now has a bounded timeout as a safety net against a hung or throwing command path holding it indefinitely. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
For context: this PR only covers fixes 1-4 from the battery review. Not addressed here, in case they're worth follow-up PRs:
Also worth noting, since it didn't fit clean batching: |
flushUsageBatch() could match zero rows (silently dropping sent/received deltas) when its access row was still sitting unflushed in accessBatch; it now flushes access first and logs on a zero-row update again. requestFullRestart()'s backed-off restart runnable (up to 5 min out) was never cancelled on disable/stop/config-change, risking a stale reload firing later. It's now a stored Runnable removed in clearRecoveryState(), guarded by a restartScheduled flag so callers that never scheduled one don't force verifyHandler's lazy init. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- WgEgress: coalesce (not drop) network-change rebinds via in-flight + dirty flags, so a change mid-rebind re-runs on the current network instead of leaving sockets bound to a departed one. - WgEgress: reset restart backoff on a confirmed-fresh handshake even when concurrent network events bump verificationGeneration, so a later breakage isn't stuck behind a stale (up to 5 min) backoff. - WgEgress: guard the delayed restart runnable on forceRestartPending so a restart already handled by another recovery path doesn't fire a redundant restart of a healthy tunnel. - DatabaseHelper: schedule a bounded time-based flush for the tail of an access/usage batch so a lone late update isn't invisible until the next reader or shutdown (and bounds unclean-kill loss to the flush window). - DatabaseHelper: preserve a specified block across last-write-wins coalescing when a later same-key update omits it; add regression test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Code-review fixes + emulator smoke test (through a483b59)Applied the findings from a review of this branch, plus one deferred battery item, and ran an emulator pass. Fixes pushedFive review findings, all in the new batching / WG-recovery mechanics:
Plus the deferred item from the battery review:
Build/unit verification: Java + Kotlin compile clean and the full Emulator smoke test (Small_Phone AVD, Android 16)Built and installed the full
Honest limitations of the emulator pass:
Still outstanding before merge
|
handleMessage forced a stop-the-world GC on every start/reload/stop/ watchdog/householding command. Drop it and let ART schedule collection itself — the explicit call only added latency and jank on the command path with no benefit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Summary
Implements the four highest-ROI fixes from a battery review of the VPN service, WireGuard egress, and database layer:
updateAccess/updateUsage(DatabaseHelper.java): mirrors the existinginsertLogbatching — repeated writes to the same access row within a 2s/50-entry window coalesce into one transaction (last-write-wins for state, summed deltas for usage), andupdateUsage's old SELECT-then-UPDATE collapses into a singleUPDATE ... SET sent = COALESCE(sent,0) + ?. Access-table readers/writers flush pending batches first for read-your-writes consistency;onDestroyflushes on shutdown so nothing is lost.insertDnsis intentionally left unbatched, since its return value synchronously gatesprepareUidIPFilters(firewall rule enforcement for newly resolved trackers) — batching it would introduce a correctness-relevant delay.WgEgress.kt): network-change rebinds now run through a single-thread executor with dedup instead of spawning an unbounded rawThreadper event. The broken→full-restart loop now backs off exponentially (20s → 40s → 80s → ... capped at 5 min) instead of retrying every ~20s forever on a captive portal or blocked UDP path. Backoff resets on any confirmed fresh handshake.ServiceSinkhole.java): bursts ofConnectivityManagercallbacks (common during Wi-Fi↔cellular handoffs) now coalesce into a single reload 1.5s after the burst settles, instead of restarting the VPN/WireGuard per callback. Cancelled cleanly on service destroy.onStartCommand's wakelock now has a 30s timeout as a safety net against a hung or throwing command path holding it indefinitely.Test plan
./gradlew compileFdroidDebugJavaWithJavacandcompileFdroidDebugKotlin— clean./gradlew testFdroidDebugUnitTest— full suite passesDatabaseHelperAccessBatchTestcovering coalescing (last-write-wins), usage accumulation, and distinct-key isolation within a batch🤖 Generated with Claude Code