temporalio/temporal · error
expect at least one rate limiter
Error message
expect at least one rate limiter
What it means
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.
Source
Thrown at common/quotas/multi_rate_limiter_impl.go:31
InfDuration = rate.InfDuration
)
type (
// MultiRateLimiterImpl is a wrapper around the limiter interface
MultiRateLimiterImpl struct {
rateLimiters []RateLimiter
recycleCh chan struct{}
}
)
var _ RateLimiter = (*MultiRateLimiterImpl)(nil)
// NewMultiRateLimiter returns a new rate limiter that have multiple stage
func NewMultiRateLimiter(
rateLimiters []RateLimiter,
) *MultiRateLimiterImpl {
if len(rateLimiters) == 0 {
panic("expect at least one rate limiter")
}
return &MultiRateLimiterImpl{
rateLimiters: rateLimiters,
recycleCh: make(chan struct{}),
}
}
// Allow immediately returns with true or false indicating if a rate limit
// token is available or not
func (rl *MultiRateLimiterImpl) Allow() bool {
return rl.AllowN(time.Now(), 1)
}
// AllowN immediately returns with true or false indicating if n rate limit
// token is available or not
func (rl *MultiRateLimiterImpl) AllowN(now time.Time, numToken int) bool {
length := len(rl.rateLimiters)
reservations := make([]Reservation, 0, length)View on GitHub (pinned to bde624efd1)
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
Example fix
// before
rl := quotas.NewMultiRateLimiter(cfg.RateLimiters)
// after
if len(cfg.RateLimiters) == 0 {
cfg.RateLimiters = []quotas.RateLimiter{quotas.NewNoopRateLimiter()}
}
rl := quotas.NewMultiRateLimiter(cfg.RateLimiters) Defensive patterns
Strategy: validation
Validate before calling
if len(rateLimiters) == 0 {
rateLimiters = []quotas.RateLimiter{quotas.NewNoopRateLimiter()}
}
rl := quotas.NewMultiRateLimiter(rateLimiters) Try / catch
func() (rl *quotas.MultiRateLimiterImpl) {
defer func() {
if r := recover(); r != nil { rl = quotas.NewMultiRateLimiter(defaultLimiter) }
}()
return quotas.NewMultiRateLimiter(limiters)
}() Prevention
- 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
When it happens
Trigger: Calling NewMultiRateLimiter(nil) or NewMultiRateLimiter([]quotas.RateLimiter{}) — e.g. when a config-driven list of rate limiters resolves to nothing.
Common situations: Empty or misparsed dynamic config for rate limiter stages; building limiters conditionally so all are skipped; feature flags disabling every limiter.
Related errors
- expect at least one rate limiter
- And requires at least 2 predicates, got %v
- Or requires at least 2 predicates, got %v
- expect at least one reservation
- Request to priority & priority to rate limiter does not matc
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/ce8ffe26aae77748.
Report an issue: GitHub.