Trip the DoH circuit breaker per attempt and drop the dead delayed-screen-off alarm - #768
Merged
Conversation
…reen-off alarm Two battery follow-ups: The DoH circuit breaker counted failed queries, but each failing query burns up to three full timeout budgets inside resolve(). A dead endpoint could keep ~30s of hung executor work alive per query for ten straight queries before the 60s cooldown tripped. Count every wasted network attempt instead (via a new optional resolve() listener), so the breaker trips after a few bad queries. Non-retryable client errors are still counted once, by the caller. ACTION_SCREEN_OFF_DELAYED was registered and cancelled but never scheduled — except by an error fallback that armed a doze-allowed RTC_WAKEUP every 15 seconds if handling a screen event threw, looping indefinitely while the underlying failure persisted. Remove the action, its receiver registration, and that fallback loop.
Rebasing onto master surfaced issue #760: getInstance()'s endpoint swap cancels in-flight calls on the outgoing client via shutdown()'s dispatcher().cancelAll(), and with per-attempt counting each canceled attempt now adds to the circuit-breaker total. That turns a routine endpoint switch into several instant "failures" against the brand-new, healthy endpoint. Skip reportFailedAttempt when the IOException came from our own cancellation (Call#isCanceled()) rather than a real network failure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
kasnder
force-pushed
the
fix/battery-followups
branch
from
August 22, 2026 10:09
b71d70a to
0e7786e
Compare
This was referenced Aug 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two battery follow-ups from the battery audit (companions to #747/#748/#749). Rebased onto current master (f520eba), which already contains #747 (DoH retries/keep-alives gated on screen state), #752 (ServiceSinkhole teardown) and #753 (DNS proxy hardening).
1. DoH circuit breaker counts attempts, not queries
Updated rationale post-#747: #747 already sets
maxRetries = 0while the screen is off, so a failing query in doze now costs one ~5s attempt, not three — this PR's per-attempt counting degenerates to the old per-query behaviour there, and #747 has already captured that battery win. The remaining value of this PR is screen-on: a dead endpoint hit by #753's larger DoH thread pool now trips the breaker in ~3–4 bad queries (3 wasted attempts each) instead of needing 10 full queries, so screen-on doesn't keep hammering (and logging around) a known-dead endpoint as long.DnsOverHttpsClient.resolve(query)gains an optionalonFailedAttemptcallback fired once per retryable failure (network error, server 5xx, unusable body) — exactly the attempts that waste their full budget. Non-retryable client errors are not reported there; the caller still counts those once via its null handling.dohFailureswithaddAndGet(max(1, attempts)), so the breaker trips after ~3–4 bad queries instead of ten, on screen-on.isPlausibleDnsQuerygate returns before DoH is ever called, so garbage/malformed queries still never reach the counter.#760 (per-attempt counting worsens the endpoint-switch false-trip) — assessed and fixed. #760 already flags that
DnsOverHttpsClient.getInstance(context, endpoint)cancels in-flight calls on the outgoing client viashutdown()'sdispatcher().cancelAll(), andDnsProxyServercounts every nullresolve()as a failure. With straight per-attempt counting that's worse than before: a query whose in-flight call gets canceled and then keeps retrying against the same doomed client can rack up allMAX_RETRIES+1attempts as "failures," so as few as ~4 in-flight queries at switch time (4×3=12) could trip the threshold-of-10 breaker against the new, healthy endpoint, versus 10 queries needed pre-#768. Fixed in this rebase:resolve()now checksCall#isCanceled()in theIOExceptionhandler and returns without callingonFailedAttemptwhen the failure was our own cancellation rather than a real network error. This directly implements the "don't count cancellations caused by our own shutdown()" direction from #760's own suggested fix (the other two #760 issues — reset-on-any-success, and the shared UDP/TCP counter — are pre-existing and out of scope here).2. Remove the half-dead
ACTION_SCREEN_OFF_DELAYEDmachineryThe action had a receiver registration and a cancel, but nothing ever scheduled it — except an error fallback in the screen-state receiver that armed a
setAndAllowWhileIdleRTC_WAKEUP every 15 seconds if handling a screen event threw, looping indefinitely in doze while the underlying failure persisted. Removed: the action constant, its registration line, and the fallback loop (the catch now just logs). Unaffected by #752's ServiceSinkhole teardown changes (different code path).Testing
DnsOverHttpsClientTest: three 5xx → 3 reports; a 400 → 0 reports (still counted once by the caller); short-body retry → 1 report then success; a canceled in-flight call → 0 reports (the DoH circuit breaker counts endpoint-switch cancellations as failures and resets on any success #760 fix).net.kollnig.missioncontrol.dns.*suite and the full unit test suite pass.