Skip to content
skyl

WithRetryAfterCap

Bounds how long a provider's Retry-After may delay a retry. Default 5m.

When a provider sends a Retry-After header, skyl honours it in preference to its own computed backoff — the provider knows when its window resets. This option bounds how far that trust extends.

Reference#

func WithRetryAfterCap(d time.Duration) Option

Parameters

  • d — the maximum delay a provider's hint may impose. Default 5m.

Caveats

  • Non-positive values are ignored, leaving the default in place.
  • This is separate from WithRetryDelay's max, which caps only skyl's own computed backoff.
  • It applies only when the provider actually sent a parseable Retry-After.
  • ParseRetryAfter handles both the seconds form and the HTTP-date form, and never returns a negative duration.

Why it is a separate bound#

Deep diveTwo different things being bounded

A provider asking for 60 seconds is a normal rate-limit window. Clamping that to a 30-second backoff ceiling means retrying before the window resets — which fails again, and burns an attempt for nothing.

But a provider asking for an hour should not silently wedge your caller for an hour. Something has gone wrong upstream, and a request handler holding a connection that long is worse than an error.

So the hint gets its own bound: generous enough to honour a real window, bounded enough that a pathological value cannot hang you.

Before this separation existed, Retry-After was clamped to the backoff ceiling and skyl systematically retried too early on exactly the failures it was designed to handle.

Usage#

Fitting a request-handler budget

goCompiles
// With 30 seconds total, honouring a 60-second hint is pointless — better to
// fail fast and shed load at the caller.
client := skyl.New(p,
	skyl.WithTimeout(12*time.Second),
	skyl.WithRetryAfterCap(3*time.Second),
)
// With 30 seconds total, honouring a 60-second hint is pointless — better to
// fail fast and shed load at the caller.
client := skyl.New(p,
	skyl.WithTimeout(12*time.Second),
	skyl.WithRetryAfterCap(3*time.Second),
)

Being patient in a batch job

goCompiles
client := skyl.New(p,
	skyl.WithMaxRetries(10),
	skyl.WithRetryAfterCap(15*time.Minute), // nobody is waiting
)
client := skyl.New(p,
	skyl.WithMaxRetries(10),
	skyl.WithRetryAfterCap(15*time.Minute), // nobody is waiting
)

Reading the hint yourself

goCompiles
var e *skyl.Error
if errors.As(err, &e) && e.RetryAfter > 0 {
	// Pass it on, so your own callers back off in step with the real window.
	w.Header().Set("Retry-After", strconv.Itoa(int(e.RetryAfter.Seconds())))
}
var e *skyl.Error
if errors.As(err, &e) && e.RetryAfter > 0 {
	// Pass it on, so your own callers back off in step with the real window.
	w.Header().Set("Retry-After", strconv.Itoa(int(e.RetryAfter.Seconds())))
}

Troubleshooting#

A retry waited five minutes

That is the default cap being reached — the provider asked for at least that long. Lower the cap if your latency budget cannot absorb it, and expect to shed load instead.

My test hangs on sandbox-status-429

The sandbox sends a real Retry-After with its 429, and the default cap is five minutes. Set WithRetryAfterCap(50 * time.Millisecond) in tests alongside a short WithRetryDelay.

Should I set this and WithRetryDelay to the same value?

Usually not. They bound different things: your patience with skyl's guess, and your patience with the provider's knowledge. The provider's hint deserves more latitude, which is why the default is 5m against 30s.

Edit this page on GitHub