{"record":{"id":"110a7548360526e6","repo":"vitessio/vitess","slug":"bug-throttle-must-not-be-called-with-a-time-of","errorCode":null,"errorMessage":"BUG: throttle() must not be called with a time of less than 1 second. now: %v","messagePattern":"BUG: throttle\\(\\) must not be called with a time of less than 1 second\\. now: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/throttler/thread_throttler.go","lineNumber":66,"sourceCode":"\t// throttle() call to be accepted after setMaxRate() has been called with a nonzero rate.\n\t// Unfortunately, if we initialize the limiter rate to 0, the internal token buffer will be\n\t// empty by the time the first throttle() call is executed and it will be denied.\n\t// Instead, we initialize the limiter rate to 1. This way the token buffer will be full (assuming\n\t// the 'now' parameter of the first throttle() call is at least 1 second) and the rate will\n\t// be reset to 0 if setMaxRate() has not been called with a nonzero rate.\n\tresult := threadThrottler{\n\t\tthreadID:          threadID,\n\t\tactualRateHistory: actualRateHistory,\n\t\tlimiter:           rate.NewLimiter(1 /* limit */, 1 /* burst */),\n\t}\n\treturn &result\n}\n\nvar oneSecond = time.Time{}.Add(1 * time.Second)\n\nfunc (t *threadThrottler) throttle(now time.Time) time.Duration {\n\tif now.Before(oneSecond) {\n\t\tpanic(fmt.Sprintf(\n\t\t\t\"BUG: throttle() must not be called with a time of less than 1 second. now: %v\",\n\t\t\tnow))\n\t}\n\n\t// Pass the limit set by the last call to setMaxRate. Limiter.SetLimitAt\n\t// is idempotent, so we can call it with the same value more than once without\n\t// issues.\n\tt.limiter.SetLimitAt(now, rate.Limit(t.maxRate.Load()))\n\n\t// Initialize or advance the current second interval when necessary.\n\tnowSecond := now.Truncate(time.Second)\n\tif t.currentSecond != nowSecond {\n\t\t// Report the number of successful (not-throttled) requests from the \"last\" second if this is\n\t\t// not the first time 'throttle' is called.\n\t\tif !t.currentSecond.IsZero() {\n\t\t\tt.actualRateHistory.addPerThread(t.threadID, record{t.currentSecond, t.currentRate})\n\t\t}\n\t\tt.currentRate = 0","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/throttler/thread_throttler.go#L48-L84","documentation":"threadThrottler.throttle() requires 'now' to be at least oneSecond (epoch + 1s) because rate limiting math operates on per-second buckets relative to a real clock. A time before that is treated as an uninitialized or zero-valued time.Time, which is a programming bug, so it panics.","triggerScenarios":"Calling ThrottlerImpl.Throttle() when the throttler's nowFunc returns a zero or near-zero time.Time — e.g. a test constructed the Throttler without setting nowFunc, or injected a clock returning time.Time{}.","commonSituations":"Unit tests (like TestThrottle_NoBurst) or benchmark harnesses that build a ThrottlerImpl with a stub clock initialized to the zero value instead of a realistic epoch-based time.","solutions":["Initialize the injected nowFunc to return times at or after oneSecond (e.g. time.Unix(1,0) or real time.Now())","If using a fake clock, base it on time.Time{}.Add(n) with n >= 1s","Check that ThrottlerImpl construction actually wires a working nowFunc instead of leaving the default nil/zero path"],"exampleFix":"// before\nnowFunc: func() time.Time { return time.Time{} }\n// after\nnowFunc: func() time.Time { return time.Time{}.Add(time.Second) }","handlingStrategy":"validation","validationCode":"if now.Before(time.Time{}.Add(time.Second)) {\n    return errors.New(\"throttler clock must return epoch+1s or later\")\n}","typeGuard":"func validClock(f func() time.Time) bool {\n    return !f().Before(time.Time{}.Add(time.Second))\n}","tryCatchPattern":"defer func() {\n    if r := recover(); r != nil {\n        log.Errorf(\"throttle panic: %v\", r)\n    }\n}()","preventionTips":["Base fake clocks on time.Time{}.Add(n) with n >= 1s","Verify nowFunc in ThrottlerImpl constructor tests","Never pass a zero-value time.Time into Throttle"],"tags":["go","panic","throttler","clock","test-harness"],"backgroundTag":"invalid-clock-argument","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}