Billionmail/BillionMail · warning
rate limiter: %w
Error message
rate limiter: %w
What it means
RateLimitedClient.Do waits on its internal rate limiter before executing an HTTP request. If limiter.Wait fails — almost always because the request context was cancelled or its deadline expired while blocked waiting for a rate-limit slot — the error is wrapped as 'rate limiter: %w'. This separates rate-wait failures from HTTP/circuit-breaker failures.
Source
Thrown at core/internal/service/video_gen/apiclient.go:42
limiter: rate.NewLimiter(rate.Limit(rps), burst),
cb: gobreaker.NewCircuitBreaker[*http.Response](gobreaker.Settings{
Name: name,
MaxRequests: 2, // half-open: allow 2 probe requests
Interval: 60 * time.Second, // rolling window for failure counting
Timeout: 30 * time.Second, // time in open state before half-open
ReadyToTrip: func(counts gobreaker.Counts) bool {
return counts.ConsecutiveFailures >= 5
},
}),
}
}
// Do executes an HTTP request with rate limiting and circuit breaking.
// Blocks until rate limiter allows, then executes through circuit breaker.
func (c *RateLimitedClient) Do(req *http.Request) (*http.Response, error) {
ctx := req.Context()
if err := c.limiter.Wait(ctx); err != nil {
return nil, fmt.Errorf("rate limiter: %w", err)
}
resp, err := c.cb.Execute(func() (*http.Response, error) {
return c.client.Do(req)
})
if err != nil {
return nil, fmt.Errorf("circuit breaker [%s]: %w", c.cb.Name(), err)
}
return resp, nil
}
View on GitHub (pinned to fc36c76c05)
Solutions
- Increase the request/context timeout so it comfortably exceeds the worst-case rate-limit wait
- Lower the request rate or increase the limiter's burst so requests don't queue long
- If the caller is cancelling intentionally, treat this as expected and skip retrying
- Check that the context isn't already expired when Do is called
- Log limiter config vs actual traffic to right-size the rate
Example fix
// before ctx, cancel := context.WithTimeout(ctx, 500*time.Millisecond) // after ctx, cancel := context.WithTimeout(ctx, 10*time.Second) // budget for rate-limit wait + request
Defensive patterns
Strategy: retry
Validate before calling
// ensure the deadline is larger than worst-case rate-limit wait
wait := time.Duration(1.0/float64(rate) * float64(len(pending)+1)) * time.Second
if deadline, ok := ctx.Deadline(); !ok || time.Until(deadline) < wait {
return errors.New("context deadline too short for rate-limited call")
} Try / catch
resp, err := client.Do(req)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
// rate-limit wait exceeded budget — retry with a longer deadline or back off
return retryWithLongerTimeout(req)
}
return err
} Prevention
- Set HTTP timeouts generously above the limiter's worst-case wait time
- Size limiter rate/burst to your actual request volume
- Don't create request contexts that are nearly expired before calling Do
- Monitor limiter queue depth/wait times in production
When it happens
Trigger: req.Context() is cancelled or times out while Do is blocked in c.limiter.Wait(ctx); the limiter has no tokens available within the context's remaining deadline.
Common situations: Client timeout shorter than the rate-limited wait; caller cancels the request mid-flight; rate limit configured too aggressively (e.g. 1 req/sec with a 500ms HTTP timeout) under burst load.
Related errors
- cartesia clone API call: %w
- cartesia TTS API call: %w
- base URL not configured
- request context is nil
- base URL accessibility test failed: %w
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/3c85e42c152a81ea.
Report an issue: GitHub.