temporalio/temporal · error
expect at least one rate limiter
Error message
expect at least one rate limiter
What it means
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.
Source
Thrown at common/quotas/multi_request_rate_limiter_impl.go:21
import (
"context"
"fmt"
"time"
)
var _ RequestRateLimiter = (*MultiRequestRateLimiterImpl)(nil)
type (
MultiRequestRateLimiterImpl struct {
requestRateLimiters []RequestRateLimiter
}
)
func NewMultiRequestRateLimiter(
requestRateLimiters ...RequestRateLimiter,
) *MultiRequestRateLimiterImpl {
if len(requestRateLimiters) == 0 {
panic("expect at least one rate limiter")
}
return &MultiRequestRateLimiterImpl{
requestRateLimiters: requestRateLimiters,
}
}
func (rl *MultiRequestRateLimiterImpl) Allow(now time.Time, request Request) bool {
length := len(rl.requestRateLimiters)
reservations := make([]Reservation, 0, length)
for _, requestRateLimiter := range rl.requestRateLimiters {
reservation := requestRateLimiter.Reserve(now, request)
if !reservation.OK() || reservation.DelayFrom(now) > 0 {
if reservation.OK() {
reservation.CancelAt(now)
}
// cancel all existing reservationView on GitHub (pinned to bde624efd1)
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
Example fix
// before
rl := quotas.NewMultiRequestRateLimiter(requestRateLimiters...)
// after
if len(requestRateLimiters) == 0 {
requestRateLimiters = []quotas.RequestRateLimiter{quotas.NewNoopRequestRateLimiter()}
}
rl := quotas.NewMultiRequestRateLimiter(requestRateLimiters...) Defensive patterns
Strategy: validation
Validate before calling
if len(requestRateLimiters) == 0 {
requestRateLimiters = []quotas.RequestRateLimiter{quotas.NewNoopRequestRateLimiter()}
}
rl := quotas.NewMultiRequestRateLimiter(requestRateLimiters...) Try / catch
func() (rl *quotas.MultiRequestRateLimiterImpl) {
defer func() {
if r := recover(); r != nil { rl = quotas.NewMultiRequestRateLimiter(defaultRequestLimiter) }
}()
return quotas.NewMultiRequestRateLimiter(limiters...)
}() Prevention
- Never spread a possibly-empty slice into NewMultiRequestRateLimiter
- Ensure NewPriorityRateLimiter callers supply at least one limiter per priority
- Validate limiter config during service init
When it happens
Trigger: 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.
Common situations: Dynamic config yielding no per-priority request rate limiters; all limiters filtered out during construction; typo causing an empty slice to be spread.
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/b5927401500e107a.
Report an issue: GitHub.