vitessio/vitess · error
SetIdleTimeout called when timer not initialized
Error message
SetIdleTimeout called when timer not initialized
What it means
ResourcePool.SetIdleTimeout panics if the pool was created without an idle timeout, because rp.idleTimer is nil in that case. The pool only spawns its periodic idle-reaping timer when a non-zero idle timeout was configured at construction, so SetIdleTimeout has nothing to reschedule. This is an intentional fail-fast for an API contract violation (doc: 'It can only be used if there was an idle timeout set when the pool was created').
Source
Thrown at go/pools/smartconnpool/benchmarking/legacy/resource_pool.go:503
close(rp.resources)
close(rp.settingResources)
}
return nil
}
func (rp *ResourcePool) recordWait(start time.Time) {
rp.waitCount.Add(1)
rp.waitTime.Add(time.Since(start).Nanoseconds())
if rp.logWait != nil {
rp.logWait(start)
}
}
// SetIdleTimeout sets the idle timeout. It can only be used if there was an
// idle timeout set when the pool was created.
func (rp *ResourcePool) SetIdleTimeout(idleTimeout time.Duration) {
if rp.idleTimer == nil {
panic("SetIdleTimeout called when timer not initialized")
}
rp.idleTimeout.Store(idleTimeout.Nanoseconds())
rp.idleTimer.SetInterval(idleTimeout / 10)
}
// StatsJSON returns the stats in JSON format.
func (rp *ResourcePool) StatsJSON() string {
return fmt.Sprintf(`{"Capacity": %v, "Available": %v, "Active": %v, "InUse": %v, "MaxCapacity": %v, "WaitCount": %v, "WaitTime": %v, "IdleTimeout": %v, "IdleClosed": %v, "MaxLifetimeClosed": %v, "Exhausted": %v}`,
rp.Capacity(),
rp.Available(),
rp.Active(),
rp.InUse(),
rp.MaxCap(),
rp.WaitCount(),
rp.WaitTime().Nanoseconds(),
rp.IdleTimeout().Nanoseconds(),
rp.IdleClosed(),View on GitHub (pinned to 01a25a7d17)
Solutions
- Set a non-zero idleTimeout when creating the ResourcePool (NewResourcePool ... idleTimeout), then call SetIdleTimeout at runtime
- Guard the call: only invoke SetIdleTimeout if an idle timeout was configured at creation
- If dynamic tuning without an initial timer is required, restructure to always start the idle timer (or recreate the pool) rather than calling SetIdleTimeout on a zero-configured pool
Example fix
// before pool := NewResourcePool(factory, 10, 10, 0) pool.SetIdleTimeout(30*time.Second) // panics // after pool := NewResourcePool(factory, 10, 10, 30*time.Second) pool.SetIdleTimeout(60*time.Second) // ok
Defensive patterns
Strategy: validation
Validate before calling
if rp.idleTimeoutWasSet { // or track your own flag at construction
rp.SetIdleTimeout(d)
} Type guard
func canSetIdleTimeout(rp *ResourcePool) bool { return rp != nil /* pool created with non-zero idleTimeout */ } Prevention
- Always construct ResourcePool with a non-zero idleTimeout if you plan to tune it later
- Wrap SetIdleTimeout behind a helper that tracks whether the pool supports it
- Document in config that idle_timeout must be set for dynamic timeout changes
When it happens
Trigger: Calling rp.SetIdleTimeout(d) on a ResourcePool created via NewResourcePool with idleTimeout == 0 (or time.Duration(0)) so rp.idleTimer was never initialized.
Common situations: Config files where idle_timeout is unset/0 but code later tries to tune the idle timeout dynamically; refactors that moved SetIdleTimeout calls onto pools constructed with default (zero) settings.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/adf0489b3db0c1b5.
Report an issue: GitHub.