{"record":{"id":"ce8ffe26aae77748","repo":"temporalio/temporal","slug":"expect-at-least-one-rate-limiter","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_rate_limiter_impl.go","lineNumber":31,"sourceCode":"\tInfDuration = rate.InfDuration\n)\n\ntype (\n\t// MultiRateLimiterImpl is a wrapper around the limiter interface\n\tMultiRateLimiterImpl struct {\n\t\trateLimiters []RateLimiter\n\t\trecycleCh    chan struct{}\n\t}\n)\n\nvar _ RateLimiter = (*MultiRateLimiterImpl)(nil)\n\n// NewMultiRateLimiter returns a new rate limiter that have multiple stage\nfunc NewMultiRateLimiter(\n\trateLimiters []RateLimiter,\n) *MultiRateLimiterImpl {\n\tif len(rateLimiters) == 0 {\n\t\tpanic(\"expect at least one rate limiter\")\n\t}\n\treturn &MultiRateLimiterImpl{\n\t\trateLimiters: rateLimiters,\n\t\trecycleCh:    make(chan struct{}),\n\t}\n}\n\n// Allow immediately returns with true or false indicating if a rate limit\n// token is available or not\nfunc (rl *MultiRateLimiterImpl) Allow() bool {\n\treturn rl.AllowN(time.Now(), 1)\n}\n\n// AllowN immediately returns with true or false indicating if n rate limit\n// token is available or not\nfunc (rl *MultiRateLimiterImpl) AllowN(now time.Time, numToken int) bool {\n\tlength := len(rl.rateLimiters)\n\treservations := make([]Reservation, 0, length)","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/common/quotas/multi_rate_limiter_impl.go#L13-L49","documentation":"NewMultiRateLimiter composes several staged RateLimiters into a MultiRateLimiterImpl. It panics when the input slice is empty because a multi-stage limiter with no stages would silently allow or reject all traffic — an invalid configuration the library refuses to construct.","triggerScenarios":"Calling NewMultiRateLimiter(nil) or NewMultiRateLimiter([]quotas.RateLimiter{}) — e.g. when a config-driven list of rate limiters resolves to nothing.","commonSituations":"Empty or misparsed dynamic config for rate limiter stages; building limiters conditionally so all are skipped; feature flags disabling every limiter.","solutions":["Ensure the rateLimiters slice has at least one limiter before calling NewMultiRateLimiter","Fall back to a default NoopRateLimiter or NewLocalRateLimiter when the configured list is empty","Fix the config source that yields an empty limiter list"],"exampleFix":"// before\nrl := quotas.NewMultiRateLimiter(cfg.RateLimiters)\n// after\nif len(cfg.RateLimiters) == 0 {\n    cfg.RateLimiters = []quotas.RateLimiter{quotas.NewNoopRateLimiter()}\n}\nrl := quotas.NewMultiRateLimiter(cfg.RateLimiters)","handlingStrategy":"validation","validationCode":"if len(rateLimiters) == 0 {\n    rateLimiters = []quotas.RateLimiter{quotas.NewNoopRateLimiter()}\n}\nrl := quotas.NewMultiRateLimiter(rateLimiters)","typeGuard":null,"tryCatchPattern":"func() (rl *quotas.MultiRateLimiterImpl) {\n    defer func() {\n        if r := recover(); r != nil { rl = quotas.NewMultiRateLimiter(defaultLimiter) }\n    }()\n    return quotas.NewMultiRateLimiter(limiters)\n}()","preventionTips":["Default to a NoopRateLimiter when config yields no limiters","Validate rate limiter config at startup, not lazily","Log a warning when falling back so misconfiguration is visible"],"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"}