Skip to content

Retry: cap total wall-clock budget; no per-request deadline today (~180s worst case) #193

Closed

Description

@TexasCoding

Summary

The retry loop has no total wall-clock budget. With current defaults (timeout=30.0, max_retries=3, retry_max_delay=30.0), a single client.markets.list() can block the caller for ~180 seconds in the pathological case: each of 4 attempts may consume up to 30s of httpx timeout + up to 30s of Full-Jitter sleep.

For a trading SDK this is a livelock-class risk — a 30s+ stall on the hot path of an automated trader is functionally an outage. The retry policy needs either (a) a hard wall-clock cap that short-circuits remaining retries once the budget is exhausted, or (b) tighter defaults with explicit worst-case latency in the docs.

Location

  • kalshi/_base_client.py:77-87 — _compute_backoff
  • kalshi/_base_client.py:130-200 (sync) and :262-330 (async) — retry loops have no elapsed-time check
  • kalshi/config.py:47-50 — defaults

Evidence

# kalshi/config.py:47-50
timeout: float = DEFAULT_TIMEOUT          # 30.0
max_retries: int = DEFAULT_MAX_RETRIES    # 3
retry_base_delay: float = 0.5
retry_max_delay: float = 30.0

Worst-case computation for a single call:

  • 4 attempts × 30s httpx timeout = 120s
  • 3 retry sleeps × up to 30s Full Jitter = up to 90s
  • Total: up to 210s of blocking on one call

Recommended fix

Add total_timeout: float | None = None (or request_deadline) to KalshiConfig. In the retry loop, check time.monotonic() - start > total_timeout before each sleep AND before each new attempt, short-circuiting with the last error.

Reasonable defaults:

  • total_timeout = 60.0 so the absolute worst case is bounded
  • OR lower retry_max_delay default to ~5s (gives ~45s worst case)

Document the budget calculation in docs/configuration.md so operators can pick saner values for their workload.

Add tests:

  • test_total_timeout_short_circuits_retries — mock time.monotonic to exceed budget after 2 attempts; assert the 3rd never fires.
  • test_total_timeout_None_preserves_legacy_behavior — unchanged retry count when total_timeout=None.

Severity & category

high / correctness

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingenhancementNew feature or requestinfraInfrastructure/tooling

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions