Documentation
¶
Overview ¶
Package polling iterates an HTTP endpoint on an interval, reusing the supplied *http.Client so the configured transport stack (RateLimit, Throttle, Retry, oauth2, ETag) applies per attempt.
Two entry points:
- Poll yields *http.Response per attempt. The caller drains and closes each body. WithDone is header/status-only; the body is consumed by the time the predicate runs.
- As[T] decodes each response into T (default JSON) and yields the typed value. The iterator owns and closes each body. Use WithDoneT to stop on the decoded value; use WithDecode to swap the decoder.
Stop conditions: ctx cancel, predicate match, WithMaxAttempts hit, WithMaxWallClock expiry, transport error, predicate panic. On MaxAttempts and MaxWallClock the iterator yields (lastResp, sentinel) so the caller can inspect headers/status of what triggered the limit; lastResp.Body has already been drained and closed by the caller's prior normal-yield iteration (per Poll's body-ownership contract). lastResp may be nil if WithChangeOnly suppressed every yield. ErrMaxWallClockExceeded wraps context.DeadlineExceeded.
Sharp edges:
- Each c.Do may itself loop through retry.Transport (default 3 attempts, 200ms-2s jitter). When polling owns the outer loop, pass retry.WithMaxAttempts(1) on the client.
- throttle.WithRequestsPerSecond below 1/interval dominates cadence; effective_interval = max(interval, 1/rps).
- With WithETagCache and an unchanged resource, As[T] decodes identical bytes per tick. WithChangeOnly silently skips those.
- gofri ctx-cancel during a secondary cooldown is observed on the next inner round-trip, not synchronously.
Determinism: production uses time.NewTimer + select-on-ctx. Tests inject WithSleepFunc / WithNowFunc. Jitter is a deterministic mid-point clamp; not applied when honoring Retry-After.
Body argument: the body []byte passed to Poll/As is not deep-copied; the iterator constructs a fresh bytes.NewReader(body) per attempt that aliases the slice. Do not mutate the slice while polling.
Retry-After interaction with WithMaxWallClock: an honored Retry-After exceeding the remaining wall-clock budget is truncated by the ctx deadline and surfaces as ErrMaxWallClockExceeded.
WithNowFunc and ctx.Deadline: WithNowFunc affects the value passed to context.WithDeadline at construction (now() + MaxWallClock); the resulting deadline still fires on real wall-clock. A frozen-past nowFunc therefore cancels immediately.
Slog field allowlist (polling_predicate_panic):
- event: "polling_predicate_panic"
- panic_type: fmt.Sprintf("%T", recovered) (type name, no payload)
No request/response headers are emitted to slog; the panic event echoes only the recovered value's Go type name.
Index ¶
- Variables
- func As[T any](ctx context.Context, c *http.Client, method, url string, headers http.Header, ...) iter.Seq2[T, error]
- func Poll(ctx context.Context, c *http.Client, method, url string, headers http.Header, ...) iter.Seq2[*http.Response, error]
- type Option
- func WithChangeOnly() Option
- func WithDecode[T any](decode func(*http.Response) (T, error)) Option
- func WithDone(predicate func(*http.Response) bool) Option
- func WithDoneT[T any](predicate func(T) bool) Option
- func WithHonorRetryAfter(b bool) Option
- func WithJitter(frac float64) Option
- func WithLogger(l *slog.Logger) Option
- func WithMaxAttempts(n int) Option
- func WithMaxWallClock(d time.Duration) Option
- func WithNowFunc(f func() time.Time) Option
- func WithSleepFunc(f func(time.Duration)) Option
Constants ¶
This section is empty.
Variables ¶
var ErrInvalidInterval = errors.New("polling: interval must be > 0")
ErrInvalidInterval is returned when interval <= 0.
var ErrInvalidOption = errors.New("polling: invalid option")
ErrInvalidOption flags malformed Option values (nil seam, n < 1, generic T mismatch).
var ErrMaxAttemptsExceeded = errors.New("polling: max attempts exceeded")
ErrMaxAttemptsExceeded is the boundary sentinel for WithMaxAttempts.
var ErrMaxWallClockExceeded = fmt.Errorf("polling: max wall-clock exceeded: %w", context.DeadlineExceeded)
ErrMaxWallClockExceeded is the boundary sentinel for WithMaxWallClock; wraps context.DeadlineExceeded.
var ErrPredicatePanic = errors.New("polling: predicate panicked")
ErrPredicatePanic is yielded when WithDone or WithDoneT panics.
Functions ¶
func As ¶
func As[T any]( ctx context.Context, c *http.Client, method, url string, headers http.Header, body []byte, interval time.Duration, opts ...Option, ) iter.Seq2[T, error]
As decodes each polling response into T (default: encoding/json). The iterator owns and closes each body via defer; decode errors yield once and stop, decoder panics propagate with the body closed. Use WithDoneT to stop on the decoded value.
func Poll ¶
func Poll( ctx context.Context, c *http.Client, method, url string, headers http.Header, body []byte, interval time.Duration, opts ...Option, ) iter.Seq2[*http.Response, error]
Poll iterates an HTTP endpoint on interval, reusing the supplied *http.Client so the transport stack applies per attempt. The caller drains and closes each yielded body. See package doc for stop conditions and sharp edges.
Types ¶
type Option ¶
type Option func(*config)
Option configures a polling iterator.
func WithChangeOnly ¶
func WithChangeOnly() Option
WithChangeOnly skips iterations where the etag layer signals a cache hit. No-op when the etag layer is not in the chain.
func WithDecode ¶
WithDecode overrides the default JSON decoder on As[T]. T must match the type parameter on As[T]; mismatches surface as ErrInvalidOption at the first iteration.
func WithDone ¶
WithDone stops the iterator when the predicate returns true. Runs after the caller's range body, so by then the body is consumed and closed; the predicate must inspect status and headers only. For body-aware stop logic, use As[T] with WithDoneT.
func WithDoneT ¶
WithDoneT stops As[T] when the predicate returns true. T must match the type parameter on As[T]; mismatches surface as ErrInvalidOption at the first iteration.
func WithHonorRetryAfter ¶
WithHonorRetryAfter (default true) honors the upstream Retry-After header. The honored value is clamped to [interval, max(interval, MaxWallClock)]; with MaxWallClock=0 the upper bound collapses to interval, ignoring saturated upstream values.
func WithJitter ¶
WithJitter applies a deterministic mid-point jitter: interval + (interval * frac / 2), clamped to [interval/2, 3*interval/2]. Frac is clamped to [0, 1]. Not applied when honoring Retry-After.
func WithLogger ¶
WithLogger receives the polling_predicate_panic event. Default discards.
func WithMaxAttempts ¶
WithMaxAttempts caps total attempts (including the first). n must be >= 1; n=1 means "one attempt only". Omitting the option leaves attempts unbounded; pair with WithMaxWallClock plus WithDoneT or WithDone for safety on unbounded runs. Zero or negative n surfaces as ErrInvalidOption at construction.
Validation aligns with retry.WithMaxAttempts (both reject n < 1); the sentinel error type differs (polling.ErrInvalidOption vs retry.ErrInvalidMaxAttempts).
func WithMaxWallClock ¶
WithMaxWallClock derives a child context deadline. Zero is unbounded. On expiry the iterator yields (lastResp, ErrMaxWallClockExceeded), which wraps context.DeadlineExceeded.
func WithNowFunc ¶
WithNowFunc is a test seam. Nil errors with ErrInvalidOption at construction.
func WithSleepFunc ¶
WithSleepFunc is a test seam. Nil errors with ErrInvalidOption at construction. Production uses time.NewTimer + select-on-ctx.