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
- Fix the caller/race so borrow is not invoked on a Busy timestamp (single-owner invariant)
- Inspect the expire/settle code paths for a missed CompareAndSwap back from timestampBusy
- 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
- Never share a timestamp across goroutines without an owner handoff protocol
- After modifying expire/settle logic, audit that every timestampBusy is CAS'ed back
- Treat Busy as non-borrowable in new call sites instead of assuming exclusivity
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
- panic: %v
- SetIdleTimeout called when timer not initialized
- negative capacity
- err
- Value.ForEachValue on non-tuple
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/b3571dc4631b292e.
Report an issue: GitHub.