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 reservation

View on GitHub (pinned to bde624efd1)

Solutions

  1. Pass at least one RequestRateLimiter to NewMultiRequestRateLimiter
  2. Guard the caller (NewPriorityRateLimiter / newShardReaderRateLimiter / newTaskRequestRateLimiter) to default to a NoopRequestRateLimiter when the list is empty
  3. 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

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


AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01). Data as JSON: /api/errors/b5927401500e107a. Report an issue: GitHub.