Documentation
¶
Overview ¶
Package ratelimit provides a shared exponential-backoff HTTP helper that honours X-RateLimit-* and Retry-After headers, with a 3-attempt and 60-second cumulative cap.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrBudgetExceeded = errors.New("ratelimit: cumulative wait budget exceeded")
ErrBudgetExceeded is returned when the cumulative wait budget would be exceeded before another retry could complete.
Functions ¶
func Do ¶
func Do(ctx context.Context, p Policy, log *slog.Logger, fn func() (*http.Response, error)) (*http.Response, error)
Do executes fn with retries on 429 and 5xx according to p. Retry-After and X-RateLimit-Reset are honoured when present; otherwise an exponential backoff with jitter (capped at ~10s) is used. ctx.Done() is observed while sleeping.
func NewHTTPTransport ¶ added in v0.4.9
NewHTTPTransport returns a fresh *http.Transport for connector use. It mirrors http.DefaultTransport but raises MaxIdleConnsPerHost from the stdlib default of 2 to 16. The default under-pools when 4+ workers concurrently hit a single API host: requests beyond the second open a fresh TCP+TLS connection and drop it on response, adding ~50-100ms per cold request. IdleConnTimeout is 60s (below GitHub's ~90s server-side close) to avoid reusing a connection the server has already torn down. All other DefaultTransport values are preserved. Issue #161.
Types ¶
type BudgetState ¶
type BudgetState struct {
Remaining int
HasRemaining bool
Limit int
ResetAt time.Time
UpdatedAt time.Time
}
BudgetState is the current X-RateLimit-* snapshot for one connector. Remaining is only authoritative when HasRemaining is true; a missing X-RateLimit-Remaining header leaves Remaining at int-zero, which would otherwise trip the predictive warning into a false positive.
type Policy ¶
type Policy struct {
MaxAttempts int
CumulativeBudget time.Duration
SecondaryRateLimitBudget time.Duration
// LowWaterMark is the X-RateLimit-Remaining threshold below which the
// transport proactively sleeps until reset + 5s to avoid mid-run stalls.
// Zero defaults to 200.
LowWaterMark int
}
Policy bounds retry behaviour. Defaults: 3 attempts shared, separate cumulative budgets per error class.
CumulativeBudget covers ordinary transient errors (429 primary rate limit, 5xx server errors). These resolve fast — typically a few seconds to a minute — so a tight budget is fine.
SecondaryRateLimitBudget covers GitHub's anti-burst 403s, whose cooldown is much longer (60s+ per retry). Keeping the budget separate means a single 60s secondary-RL wait doesn't eat the budget for subsequent transient retries, and gives realistic headroom for hammered-token cooldowns.
func DefaultPolicy ¶
func DefaultPolicy() Policy
DefaultPolicy returns the 3-attempt policy with per-error-class budgets: 60s for transient errors (429 / 5xx), 600s for secondary rate limits (GitHub anti-burst cooldowns). The split lets a long secondary-RL wait run without starving the transient-error budget — and vice versa.
type Transport ¶
type Transport struct {
Base http.RoundTripper
Policy Policy
Log *slog.Logger
// Sink receives progress.RateLimit and progress.Retry events on
// wait/retry. Nil sink (zero value) silently no-ops; existing slog
// output is unchanged either way. Spec docs/spec.md:464-469.
Sink progress.Sink
// Connector overrides the host→connector mapping used to label
// emitted events and budget snapshots. Empty falls back to
// hostToConnector(req.URL.Host).
Connector string
// contains filtered or unexported fields
}
Transport is an http.RoundTripper that retries per Policy. Install it as httpClient.Transport so the entire connector benefits from the helper without per-call wrapping.
func (*Transport) SetGQLPacing ¶ added in v0.4.4
SetGQLPacing marks the transport to sleep before the next request until the supplied time, attributing the wait to the GraphQL low-water-mark. Called by costInterceptor when throttleStatus.remaining falls below the GraphQL low-water mark. The pacing sleep fires at the start of the next RoundTrip call, matching the REST low-water path.
func (*Transport) Snapshot ¶
func (t *Transport) Snapshot() map[string]BudgetState
Snapshot returns the current rate-limit budget per connector. Empty map if no rate-limit headers have been observed.
func (*Transport) UpdateGQLBudget ¶ added in v0.4.4
UpdateGQLBudget records the current GraphQL cost-unit budget under the "github-graphql" connector key in the budget tracker, enabling Snapshot and maybeEmitPredictiveWarning to operate on GQL quota independently of the REST X-RateLimit-Remaining budget.