package proxy // retry.go holds the failover helpers used by handler.go. The retry loop itself // lives inside Handler.proxyWithFailover (see handler.go) because it needs // tight control over the moment a response starts streaming vs. is rejected // pre-stream. This file documents the invariants the loop must maintain. // Streaming invariant // =================== // Once an upstream account has returned a 2xx status AND the proxy has started // writing the response body to the client (a single byte flushed), the request // is committed: we MUST NOT switch accounts for that request. Any mid-stream // upstream error is surfaced to the client as-is (truncated response); we never // attempt to "restart" a streamed request on a different account, because the // client has already received partial output and a retry would duplicate it. // // Pre-stream failover // ------------------- // The window in which we CAN retry on another account is exactly: // 1. The upstream HTTP request returned an error (network, timeout, EOF // before any response). // 2. The upstream returned 429 (Too Many Requests) — we mark the account // cooldown and try the next. // 3. The upstream returned 5xx — we try the next account WITHOUT marking // cooldown (5xx may be transient and is not necessarily a rate limit). // // Once copyResponse has called WriteHeader, no further retries are possible // for this request.