Skip to content

Polish bundle: auth + transport small items (sign-executor race, Retry-After negative) #267

Closed

Description

@TexasCoding

Summary

Small transport / auth quality improvements bundled for a single PR.

Item 1 — _get_sign_executor checks _closed outside the lock

If sign_request_async and close() race, thread A can read _closed=False, then close() runs end-to-end (sets _closed=True, shuts down + nulls the executor), then thread A enters the double-checked init under the lock and constructs a brand-new ThreadPoolExecutor on a closed auth. That executor never gets shut down (close() already returned and won't be called again), defeating the documented terminality and leaving a 2-thread pool dangling until interpreter shutdown.

Location: kalshi/auth.py:289-303, kalshi/auth.py:331-344.

Fix: Move the self._closed check inside the lock (or recheck after acquiring it), and raise without instantiating a new executor when _closed is True.

Item 2 — Retry-After delta-seconds vs HTTP-date treatment of "past/negative" is inconsistent

# delta-seconds form: negative or NaN -> None -> falls back to computed backoff
retry_after_val = float(retry_after)
if retry_after_val < 0 or not math.isfinite(retry_after_val):
    retry_after_val = None

# HTTP-date form: past date -> 0.0 -> retry immediately
delta = (dt - datetime.now(tz=UTC)).total_seconds()
retry_after_val = max(0.0, delta)

A server (or proxy with clock skew) that emits Retry-After: -5 waits the computed exponential backoff, while the semantically equivalent Retry-After: <past HTTP-date> retries immediately. Tests that pin one shape will silently disagree with the other.

Location: kalshi/_base_client.py:84-120.

Fix: Pick one policy and apply to both — clamping to 0.0 in both branches is the more permissive choice and matches the existing date-form behavior; alternatively treat negative/past as None in both.

Severity & category

low / polish, reliability

Metadata

Metadata

Assignees

No one assigned

    Labels

    polishCode-quality and DX improvements; non-functional

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions