{"record":{"id":"b5927401500e107a","repo":"temporalio/temporal","slug":"expect-at-least-one-rate-limiter-b59274","errorCode":null,"errorMessage":"expect at least one rate limiter","messagePattern":"expect at least one rate limiter","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"common/quotas/multi_request_rate_limiter_impl.go","lineNumber":21,"sourceCode":"import (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n)\n\nvar _ RequestRateLimiter = (*MultiRequestRateLimiterImpl)(nil)\n\ntype (\n\tMultiRequestRateLimiterImpl struct {\n\t\trequestRateLimiters []RequestRateLimiter\n\t}\n)\n\nfunc NewMultiRequestRateLimiter(\n\trequestRateLimiters ...RequestRateLimiter,\n) *MultiRequestRateLimiterImpl {\n\tif len(requestRateLimiters) == 0 {\n\t\tpanic(\"expect at least one rate limiter\")\n\t}\n\treturn &MultiRequestRateLimiterImpl{\n\t\trequestRateLimiters: requestRateLimiters,\n\t}\n}\n\nfunc (rl *MultiRequestRateLimiterImpl) Allow(now time.Time, request Request) bool {\n\tlength := len(rl.requestRateLimiters)\n\treservations := make([]Reservation, 0, length)\n\n\tfor _, requestRateLimiter := range rl.requestRateLimiters {\n\t\treservation := requestRateLimiter.Reserve(now, request)\n\t\tif !reservation.OK() || reservation.DelayFrom(now) > 0 {\n\t\t\tif reservation.OK() {\n\t\t\t\treservation.CancelAt(now)\n\t\t\t}\n\n\t\t\t// cancel all existing reservation","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/common/quotas/multi_request_rate_limiter_impl.go#L3-L39","documentation":"NewMultiRequestRateLimiter combines multiple RequestRateLimiters into one. It panics on a variadic argument list of length 0, since a multi request rate limiter without any underlying limiters is an invalid construction.","triggerScenarios":"Calling NewMultiRequestRateLimiter() with no arguments, or passing a spread slice (NewMultiRequestRateLimiter(limiters...)) where limiters is empty — typically from NewPriorityRateLimiter with an empty per-priority list or from shard/task request rate limiter setup with no limiters configured.","commonSituations":"Dynamic config yielding no per-priority request rate limiters; all limiters filtered out during construction; typo causing an empty slice to be spread.","solutions":["Pass at least one RequestRateLimiter to NewMultiRequestRateLimiter","Guard the caller (NewPriorityRateLimiter / newShardReaderRateLimiter / newTaskRequestRateLimiter) to default to a NoopRequestRateLimiter when the list is empty","Fix upstream config so at least one request rate limiter is configured"],"exampleFix":"// before\nrl := quotas.NewMultiRequestRateLimiter(requestRateLimiters...)\n// after\nif len(requestRateLimiters) == 0 {\n    requestRateLimiters = []quotas.RequestRateLimiter{quotas.NewNoopRequestRateLimiter()}\n}\nrl := quotas.NewMultiRequestRateLimiter(requestRateLimiters...)","handlingStrategy":"validation","validationCode":"if len(requestRateLimiters) == 0 {\n    requestRateLimiters = []quotas.RequestRateLimiter{quotas.NewNoopRequestRateLimiter()}\n}\nrl := quotas.NewMultiRequestRateLimiter(requestRateLimiters...)","typeGuard":null,"tryCatchPattern":"func() (rl *quotas.MultiRequestRateLimiterImpl) {\n    defer func() {\n        if r := recover(); r != nil { rl = quotas.NewMultiRequestRateLimiter(defaultRequestLimiter) }\n    }()\n    return quotas.NewMultiRequestRateLimiter(limiters...)\n}()","preventionTips":["Never spread a possibly-empty slice into NewMultiRequestRateLimiter","Ensure NewPriorityRateLimiter callers supply at least one limiter per priority","Validate limiter config during service init"],"tags":["go","panic","rate-limiting","argument-validation"],"backgroundTag":"empty-rate-limiter-config","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}