v2026.06.07 — Sonos: one SOAP failure no longer drops to local #88

Merged
bvandeusen merged 1 commits from dev into main 2026-06-07 19:14:46 -04:00
Owner

Single-fix release on the per-day mutable CalVer scheme.

Sonos — a single transport SOAP failure no longer reverts to local (Android)

When the phone was locked, a transport command (lock-screen / Bluetooth / watch media button, or a queue-resync play) issued during a WiFi power-save stall would hit the 2 s connect timeout and throw. handleSoapFailure then cancelled the poll loop and fired onDrop on that single failure — showing "Disconnected from <Sonos>" and reverting to local playback, even though the renderer was perfectly reachable. This bypassed the poll loop's deliberate 30-consecutive-failure tolerance (DROP_THRESHOLD, bumped from 3 precisely for screen-off WiFi sleep / Doze).

Fix — make the 1 Hz poll loop the sole drop arbiter:

  • Transport SOAP commands retry transient IO failures (retryTransientIo: 3 attempts, 400 ms backoff) so a brief WiFi stall lands the command once WiFi wakes instead of abandoning it. A SoapFaultException (renderer answered and rejected the action) is not retried — the device is alive.
  • handleTransportFailure no longer cancels the poll loop or fires onDrop; it logs and nudges an immediate poll so the UI reconciles to Sonos's actual state. If the renderer is truly gone, the poll loop trips the drop on its own via DROP_THRESHOLD.
  • retryTransientIo extracted as an internal top-level fn with a unit test (first-success, retry-then-succeed, exhaust-and-rethrow, no-retry-on-SoapFault).

Verify on device

  • Play to a Sonos speaker, lock the phone, let WiFi sleep, then hit a lock-screen / Bluetooth / watch transport button → the command lands (once WiFi wakes); the app stays routed to Sonos instead of flashing "Disconnected" and reverting to local.
  • Power off the Sonos mid-session → after ~30 s of failed polls the app drops to local on its own (the deliberate tolerance still fires).
Single-fix release on the per-day mutable CalVer scheme. ## Sonos — a single transport SOAP failure no longer reverts to local (Android) When the phone was locked, a transport command (lock-screen / Bluetooth / watch media button, or a queue-resync `play`) issued during a WiFi power-save stall would hit the 2 s connect timeout and throw. `handleSoapFailure` then cancelled the poll loop and fired `onDrop` on that single failure — showing "Disconnected from \<Sonos\>" and reverting to local playback, even though the renderer was perfectly reachable. This bypassed the poll loop's deliberate 30-consecutive-failure tolerance (`DROP_THRESHOLD`, bumped from 3 precisely for screen-off WiFi sleep / Doze). **Fix — make the 1 Hz poll loop the sole drop arbiter:** - Transport SOAP commands retry transient IO failures (`retryTransientIo`: 3 attempts, 400 ms backoff) so a brief WiFi stall lands the command once WiFi wakes instead of abandoning it. A `SoapFaultException` (renderer answered and rejected the action) is **not** retried — the device is alive. - `handleTransportFailure` no longer cancels the poll loop or fires `onDrop`; it logs and nudges an immediate poll so the UI reconciles to Sonos's actual state. If the renderer is truly gone, the poll loop trips the drop on its own via `DROP_THRESHOLD`. - `retryTransientIo` extracted as an internal top-level fn with a unit test (first-success, retry-then-succeed, exhaust-and-rethrow, no-retry-on-SoapFault). ## Verify on device - Play to a Sonos speaker, lock the phone, let WiFi sleep, then hit a lock-screen / Bluetooth / watch transport button → the command lands (once WiFi wakes); the app stays routed to Sonos instead of flashing "Disconnected" and reverting to local. - Power off the Sonos mid-session → after ~30 s of failed polls the app drops to local on its own (the deliberate tolerance still fires).
bvandeusen added 1 commit 2026-06-07 19:14:41 -04:00
fix(android): stop single SOAP failure from dropping Sonos to local
android / Build + lint + test (push) Successful in 3m44s
dabca3ad24
When the phone was locked, a transport command (lock-screen/Bluetooth/
watch media button, or queue-resync play) issued during a WiFi power-save
stall would hit the 2s connect timeout and throw. handleSoapFailure then
cancelled the poll loop and fired onDrop on that single failure -- showing
"Disconnected from <Sonos>" and reverting to local playback, even though
the renderer was perfectly reachable. This bypassed the poll loop's
deliberate 30-consecutive-failure tolerance (DROP_THRESHOLD, bumped from 3
precisely for screen-off WiFi sleep / Doze).

Make the 1 Hz poll loop the sole drop arbiter:
- Transport SOAP commands retry transient IO failures (retryTransientIo:
  3 attempts, 400ms backoff) so a brief WiFi stall lands the command once
  WiFi wakes instead of abandoning it. A SoapFaultException (renderer
  answered, rejected the action) is not retried -- the device is alive.
- handleTransportFailure no longer cancels the poll loop or fires onDrop;
  it logs and nudges an immediate poll so the UI reconciles to Sonos's
  actual state. If the renderer is truly gone, the poll loop trips the
  drop on its own via DROP_THRESHOLD.

Extract retryTransientIo as an internal top-level fn + unit test covering
first-success, retry-then-succeed, exhaust-and-rethrow, and no-retry-on-
SoapFault. Refresh now-stale drop-heuristic comments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bvandeusen merged commit 962b4dbc8c into main 2026-06-07 19:14:46 -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#88