Skip to content

feat(rpc): add bounded retry for transient GET failures - #104

Merged
randileeharper merged 1 commit into
mainfrom
feat/rpc-retry
Jun 28, 2026
Merged

feat(rpc): add bounded retry for transient GET failures#104
randileeharper merged 1 commit into
mainfrom
feat/rpc-retry

Conversation

@randileeharper

Copy link
Copy Markdown
Owner

Summary

Closes #85.

CiderRpcClient._request made a single HTTP attempt per Cider call, so transient local failures (Cider restarting, a brief network hiccup) turned into user-visible errors even for idempotent reads like GET playback status. This adds a small, bounded retry for transient failures on safe read methods.

Behavior

  • GET requests are retried on transient failures (connection errors and 5xx server errors) using exponential backoff (0.1 * 2**attempt), up to cider_retry_count retries (default 2, for 3 total attempts).
  • POST writes are never retried. Cider playback POSTs (play, next, volume, queue) and run_amapi_v3 POSTs (including catalog/library searches) are stateful and not known to be idempotent, so a retry could duplicate a side effect.
  • 4xx client errors are never retried — they are definitive and retrying would mask a genuine bad request.
  • The failure_callback now fires once after retries are exhausted, rather than per attempt, so a transient blip doesn't flood failure reports.

Implementation

This mirrors the existing retry pattern in vesper/historian.py (HttpHistorianSink):

  • New cider_retry_count: int = Field(default=2, ge=0) setting in Settings, surfaced in sanitized(), analogous to historian_retry_count.
  • CiderRpcClient.__init__ takes an injectable sleep: Callable[[float], None] (defaults to time.sleep) so tests run without real delays.
  • _request wraps a single-attempt helper _send_once. A private _TransientRequestError signals retriable failures (connection errors + 5xx); definitive failures (4xx, non-JSON) raise CiderRpcError immediately from _send_once.

Test commands run

.venv/bin/python -m pytest -q                  # 270 passed (was 256)
.venv/bin/python -m ruff check vesper tests   # All checks passed
.venv/bin/python -m mypy vesper               # Success: no issues found in 37 source files

13 new tests in tests/test_rpc.py cover: GET retry on connection error / 5xx then success, retry exhaustion, exponential backoff timing, POST never retried (connection error and 5xx), 4xx never retried, 204 handling, single failure-callback invocation after exhaustion, and cider_retry_count=0 disabling retries.

CiderRpcClient._request previously made a single HTTP attempt per Cider
call, so transient local failures (Cider restarting, brief network
hiccup) turned into user-visible errors even for idempotent reads.

Add a small, bounded retry that mirrors the existing historian retry
pattern (cider_retry_count setting, injectable sleep, exponential
backoff 0.1 * 2**attempt). Only safe, idempotent GET requests are
retried; POST writes (play, next, volume, queue) and 4xx client errors
are never retried. Transient failures are connection errors and 5xx
server errors; the failure callback now fires once after retries are
exhausted rather than per attempt.

Closes #85.
@randileeharper
randileeharper merged commit 38b1380 into main Jun 28, 2026
1 check passed
@randileeharper
randileeharper deleted the feat/rpc-retry branch June 28, 2026 18:15
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.

CiderRpcClient has no retry for transient failures

1 participant