{"record":{"id":"3c85e42c152a81ea","repo":"Billionmail/BillionMail","slug":"rate-limiter-w","errorCode":null,"errorMessage":"rate limiter: %w","messagePattern":"rate limiter: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"core/internal/service/video_gen/apiclient.go","lineNumber":42,"sourceCode":"\t\tlimiter: rate.NewLimiter(rate.Limit(rps), burst),\n\t\tcb: gobreaker.NewCircuitBreaker[*http.Response](gobreaker.Settings{\n\t\t\tName:        name,\n\t\t\tMaxRequests: 2,                // half-open: allow 2 probe requests\n\t\t\tInterval:    60 * time.Second, // rolling window for failure counting\n\t\t\tTimeout:     30 * time.Second, // time in open state before half-open\n\t\t\tReadyToTrip: func(counts gobreaker.Counts) bool {\n\t\t\t\treturn counts.ConsecutiveFailures >= 5\n\t\t\t},\n\t\t}),\n\t}\n}\n\n// Do executes an HTTP request with rate limiting and circuit breaking.\n// Blocks until rate limiter allows, then executes through circuit breaker.\nfunc (c *RateLimitedClient) Do(req *http.Request) (*http.Response, error) {\n\tctx := req.Context()\n\tif err := c.limiter.Wait(ctx); err != nil {\n\t\treturn nil, fmt.Errorf(\"rate limiter: %w\", err)\n\t}\n\n\tresp, err := c.cb.Execute(func() (*http.Response, error) {\n\t\treturn c.client.Do(req)\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"circuit breaker [%s]: %w\", c.cb.Name(), err)\n\t}\n\treturn resp, nil\n}\n","sourceCodeStart":24,"sourceCodeEnd":53,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/video_gen/apiclient.go#L24-L53","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nctx, cancel := context.WithTimeout(ctx, 500*time.Millisecond)\n// after\nctx, cancel := context.WithTimeout(ctx, 10*time.Second) // budget for rate-limit wait + request","handlingStrategy":"retry","validationCode":"// ensure the deadline is larger than worst-case rate-limit wait\nwait := time.Duration(1.0/float64(rate) * float64(len(pending)+1)) * time.Second\nif deadline, ok := ctx.Deadline(); !ok || time.Until(deadline) < wait {\n    return errors.New(\"context deadline too short for rate-limited call\")\n}","typeGuard":null,"tryCatchPattern":"resp, err := client.Do(req)\nif err != nil {\n    if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {\n        // rate-limit wait exceeded budget — retry with a longer deadline or back off\n        return retryWithLongerTimeout(req)\n    }\n    return err\n}","preventionTips":["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"],"tags":["rate-limiting","context-canceled","http","timeout"],"backgroundTag":"context-deadline-exceeded","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}