vitessio/vitess · error

timestampBusy when borrowing a time

Error message

timestampBusy when borrowing a time

What it means

The timestamp state machine (used by idle-timeout tracking) only allows CAS transitions between a numeric stamp and the special timestampBusy sentinel. borrow() panics if it observes timestampBusy, which is impossible in correct usage because only the single goroutine that set Busy is allowed to read it and settle it back. Hitting this panic means two owners are contending for the same timestamp or a settle/timeout path double-used it.

Source

Thrown at go/pools/smartconnpool/timestamp.go:89

	return monotonicNow() - t.get()
}

// update sets this timestamp's value to the current monotonic time
func (t *timestamp) update() {
	t.nano.Store(int64(monotonicNow()))
}

// borrow attempts to borrow this timestamp atomically.
// It only succeeds if we can ensure that nobody else has marked
// this timestamp as expired. When succeeded, the timestamp
// is cleared as "busy" as it no longer tracks an expiration point.
func (t *timestamp) borrow() bool {
	stamp := t.nano.Load()
	switch stamp {
	case timestampExpired:
		return false
	case timestampBusy:
		panic("timestampBusy when borrowing a time")
	default:
		return t.nano.CompareAndSwap(stamp, timestampBusy)
	}
}

// expired attempts to atomically expire this timestamp.
// It only succeeds if we can ensure the timestamp hasn't been
// concurrently expired or borrowed.
func (t *timestamp) expired(now time.Duration, timeout time.Duration) bool {
	stamp := t.nano.Load()
	if stamp == timestampExpired {
		return false
	}
	if stamp == timestampBusy {
		return false
	}
	if now-time.Duration(stamp) > timeout {
		return t.nano.CompareAndSwap(stamp, timestampExpired)

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Fix the caller/race so borrow is not invoked on a Busy timestamp (single-owner invariant)
  2. Inspect the expire/settle code paths for a missed CompareAndSwap back from timestampBusy
  3. Add synchronization or fall back to returning false (treat Busy as unavailable) if concurrent borrows are expected

Example fix

// before
case timestampBusy:
    panic("timestampBusy when borrowing a time")
// after (if concurrency is expected)
case timestampBusy:
    return false // busy, not borrowable
Defensive patterns

Strategy: validation

Validate before calling

// before relying on borrow(), ensure single-owner usage:
// only the goroutine that owns the resource should call borrow/settle

Type guard

func borrowable(t *timestamp) bool {
    s := t.nano.Load()
    return s != timestampBusy && s != timestampExpired
}

Prevention

When it happens

Trigger: Calling borrow() while another goroutine already holds the timestamp in timestampBusy state — i.e. concurrent borrow/expire racing without honoring the single-owner invariant, or a bug in expire/settle that left the stamp stuck at Busy.

Common situations: Races introduced by modifying the idle-timeout expiration logic; reusing a resource's timestamp after its owner timed out; lock-free code changes in smartconnpool.

Related errors


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