ratelimit

package
v0.4.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 12, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

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

View Source
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.

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) RoundTrip

func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip implements http.RoundTripper.

func (*Transport) SetGQLPacing added in v0.4.4

func (t *Transport) SetGQLPacing(until time.Time)

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

func (t *Transport) UpdateGQLBudget(remaining int, resetAt time.Time, sink progress.Sink)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL