fix(android): hysteresis on /healthz reachable signal #82

Merged
bvandeusen merged 1 commits from dev into main 2026-06-04 14:23:49 -04:00
Owner

Hotfix for the false-positive "Server unreachable" banner that landed in v2026.06.04's re-cut.

The single-failure flip-to-false produced two false-positive cases:

  1. Startup raceAuthStore.baseUrl loads from Room asynchronously, so the first runOnce() can fire against AuthStore.DEFAULT_BASE_URL (http://localhost:8080) before the real server URL has hydrated. One failure was enough to lock the banner on for the next 5 minutes.
  2. Deployments whose reverse proxy routes only /api/* to the Go server/healthz never reaches the handler, so polls fail forever even though every real /api/* call succeeds. User sees "Server unreachable" permanently and OfflineGatedDataSource starts throwing OfflineException on every audio cache miss, silently breaking playback of uncached tracks.

Fix: require 3 consecutive failures (~15 min at the 5-min poll cadence) before flipping reachable=false; any single success resets the counter. Adds Timber.w/i at the flip transitions so operator logcat can diagnose genuine outages.

Test plan

  • Install on a device pointed at a working server → no false-positive banner during the first 5 minutes after launch.
  • Disable WiFi → Offline banner shows (driven by ConnectivityObserver, instant).
  • Re-enable WiFi → banner clears.
  • On deployments where /healthz is permanently unreachable but /api/* works → banner appears only after ~15 minutes of confirmed failures; logcat shows Timber.w "flipping reachable=false" at the transition.
Hotfix for the false-positive "Server unreachable" banner that landed in v2026.06.04's re-cut. The single-failure flip-to-false produced two false-positive cases: 1. **Startup race** — `AuthStore.baseUrl` loads from Room asynchronously, so the first `runOnce()` can fire against `AuthStore.DEFAULT_BASE_URL` (`http://localhost:8080`) before the real server URL has hydrated. One failure was enough to lock the banner on for the next 5 minutes. 2. **Deployments whose reverse proxy routes only `/api/*` to the Go server** — `/healthz` never reaches the handler, so polls fail forever even though every real `/api/*` call succeeds. User sees "Server unreachable" permanently and `OfflineGatedDataSource` starts throwing `OfflineException` on every audio cache miss, silently breaking playback of uncached tracks. Fix: require 3 consecutive failures (~15 min at the 5-min poll cadence) before flipping `reachable=false`; any single success resets the counter. Adds `Timber.w`/`i` at the flip transitions so operator logcat can diagnose genuine outages. ## Test plan - [ ] Install on a device pointed at a working server → no false-positive banner during the first 5 minutes after launch. - [ ] Disable WiFi → `Offline` banner shows (driven by `ConnectivityObserver`, instant). - [ ] Re-enable WiFi → banner clears. - [ ] On deployments where `/healthz` is permanently unreachable but `/api/*` works → banner appears only after ~15 minutes of confirmed failures; logcat shows `Timber.w "flipping reachable=false"` at the transition.
bvandeusen added 1 commit 2026-06-04 14:23:44 -04:00
The single-failure flip-to-false produced two false-positive permanent
banner cases:

1. Startup race — AuthStore.baseUrl loads from Room asynchronously, so the
   first runOnce() can fire against AuthStore.DEFAULT_BASE_URL
   ("http://localhost:8080") before the real server URL has hydrated. One
   failure was enough to lock the banner on for the next 5 minutes.

2. Deployments whose reverse proxy routes only /api/* to the Go server —
   /healthz never reaches the handler, so /healthz polls fail forever even
   though every real /api/* call succeeds. User sees "Server unreachable"
   permanently and OfflineGatedDataSource starts throwing OfflineException
   on every audio cache miss, silently breaking playback of uncached
   tracks.

Now we require 3 consecutive failures (~15 min at the 5-min poll cadence)
before flipping reachable=false, and any single success resets the
counter. Adds Timber.w/i at the flip transitions so operator logcat can
diagnose genuine outages.
bvandeusen merged commit 8b08482d13 into main 2026-06-04 14:23:49 -04:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bvandeusen/minstrel#82