vitessio/vitess · error

BUG: limiter was unable to reserve an event. threadThrottler

Error message

BUG: limiter was unable to reserve an event. threadThrottler: %+v, reservation:%v

What it means

After reserving an event with the rate limiter (ReserveN), throttle() expects reservation.OK() to be true; a false OK means the limiter cannot ever grant the event (typically limiter burst/limit configured to zero or negative, or limiter in a failed state). The throttler treats this as an unrecoverable BUG and panics.

Source

Thrown at go/vt/throttler/thread_throttler.go:100

			t.actualRateHistory.addPerThread(t.threadID, record{t.currentSecond, t.currentRate})
		}
		t.currentRate = 0
		t.currentSecond = nowSecond
	}

	if t.limiter.Limit() == 0 {
		// If the limit is 0 this request won't be let through. However, the caller
		// should poll again in the future in case the limit has changed.
		return 1 * time.Second
	}

	// Figure out how long to backoff: We use the limiter.ReserveN() method to reserve an event.
	// The returned reservation contains the backoff delay. We cancel the reservation if the
	// delay is greater than 0, since the caller is expected to call throttle() again at that time
	// rather than proceed.
	reservation := t.limiter.ReserveN(now, 1)
	if !reservation.OK() {
		panic(fmt.Sprintf("BUG: limiter was unable to reserve an event. "+
			"threadThrottler: %+v, reservation:%v", t, *reservation))
	}
	waitDuration := reservation.DelayFrom(now)
	if waitDuration <= 0 {
		t.currentRate++
		return NotThrottled
	}
	reservation.CancelAt(now)
	return waitDuration
}

// setMaxRate sets the maximum rate for the next time throttle() is called.
// setMaxRate() can be called concurrently with other methods of this object.
func (t *threadThrottler) setMaxRate(newRate int64) {
	t.maxRate.Store(newRate)
}

// maxRate returns the rate set by the last call to setMaxRate().

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Ensure setMaxRate is only called with strictly positive values
  2. Validate rate configuration at startup (reject 0 or negative rates before creating the throttler)
  3. If a 0-rate 'pause' is intended, gate it before calling Throttle instead of feeding it to the limiter
  4. Inspect the panic dump's threadThrottler state to confirm the configured limit/burst

Example fix

// before
throttler.SetMaxRate(0)
// after
if maxRate <= 0 { maxRate = defaultMaxRate }
throttler.SetMaxRate(maxRate)
Defensive patterns

Strategy: validation

Validate before calling

if maxRate <= 0 {
    return fmt.Errorf("maxRate must be positive, got %d", maxRate)
}
throttler.SetMaxRate(maxRate)

Type guard

func validRate(r float64) bool { return r > 0 }

Try / catch

defer func() {
    if r := recover(); r != nil {
        log.Errorf("throttle reservation panic: %v", r)
    }
}()

Prevention

When it happens

Trigger: Calling ThrottlerImpl.Throttle() on a threadThrottler whose rate.Limiter rejects the reservation — commonly after setMaxRate(0) or a limiter constructed with limit <= 0 / burst < 1.

Common situations: Configuring max rate to 0 via the throttler's SetMaxRate in a test or via misconfigured flags; passing negative rate values from config parsing bugs.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/170888e493ad49b5. Report an issue: GitHub.