temporalio/temporal · error

expect at least one reservation

Error message

expect at least one reservation

What it means

NewMultiReservation wraps the results of reserving from multiple rate limiters. It panics when ok==true but the reservations slice is empty — an inconsistent state, since a successful multi-reservation must contain at least one underlying reservation.

Source

Thrown at common/quotas/multi_reservation_impl.go:21

import (
	"time"
)

type (
	MultiReservationImpl struct {
		ok           bool
		reservations []Reservation
	}
)

var _ Reservation = (*MultiReservationImpl)(nil)

func NewMultiReservation(
	ok bool,
	reservations []Reservation,
) *MultiReservationImpl {
	if ok && len(reservations) == 0 {
		panic("expect at least one reservation")
	}
	return &MultiReservationImpl{
		ok:           ok,
		reservations: reservations,
	}
}

// OK returns whether the limiter can provide the requested number of tokens
func (r *MultiReservationImpl) OK() bool {
	return r.ok
}

// Cancel indicates that the reservation holder will not perform the reserved action
// and reverses the effects of this Reservation on the rate limit as much as possible
func (r *MultiReservationImpl) Cancel() {
	r.CancelAt(time.Now())
}

View on GitHub (pinned to bde624efd1)

Solutions

  1. Only pass ok=true when at least one reservation was collected (len(reservations) > 0)
  2. Fix the underlying rate limiter that returns a successful reservation without a Reservation object
  3. If no limiters participated, pass ok=false instead

Example fix

// before
res := quotas.NewMultiReservation(ok, collected)
// after
ok = ok && len(collected) > 0
res := quotas.NewMultiReservation(ok, collected)
Defensive patterns

Strategy: validation

Validate before calling

ok = ok && len(reservations) > 0
res := quotas.NewMultiReservation(ok, reservations)

Try / catch

func() (r quotas.Reservation) {
    defer func() {
        if recover() != nil { r = failedReservation }
    }()
    return quotas.NewMultiReservation(ok, reservations)
}()

Prevention

When it happens

Trigger: Calling NewMultiReservation(true, nil) or NewMultiReservation(true, []quotas.Reservation{}) — e.g. when Reserve loops over limiters but collects no reservations while still reporting ok.

Common situations: Buggy custom RateLimiter whose Reserve returns ok=true with a nil reservation; assembling reservations from a filtered list that dropped all entries.

Related errors


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