vitessio/vitess · error

SetIdleTimeout called when timer not initialized

Error message

SetIdleTimeout called when timer not initialized

What it means

ResourcePool.SetIdleTimeout can only adjust an already-configured idle timeout because it relies on the idleTimer created at pool construction. If the pool was created with IdleTimeout == 0 (feature disabled), idleTimer is nil and the call panics. The doc comment explicitly restricts this method to pools created with an idle timeout.

Source

Thrown at go/pools/resource_pool.go:346

	if capacity == 0 {
		close(rp.resources)
	}
	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

  1. Set a nonzero IdleTimeout when constructing the ResourcePool so idleTimer is initialized.
  2. Recreate the pool with the new idle timeout instead of mutating it at runtime.
  3. Guard calls: only invoke SetIdleTimeout when the pool was created with an idle timeout.

Example fix

// before
pool := pools.NewResourcePool(f, cap, cap, 0) // no idle timeout
pool.SetIdleTimeout(30 * time.Second) // panics
// after
pool := pools.NewResourcePool(f, cap, cap, 30*time.Second) // idle timeout set at creation
pool.SetIdleTimeout(60 * time.Second) // OK
Defensive patterns

Strategy: validation

Validate before calling

if rp.IDleTimeout == 0 { // pool created without idle timeout
    return errors.New("SetIdleTimeout requires a pool created with an idle timeout")
}

Try / catch

defer func() { if r := recover(); r != nil { err = fmt.Errorf("SetIdleTimeout failed: %v", r) } }()

Prevention

When it happens

Trigger: Creating a pool via NewResourcePool (or NewGenericResourcePool) without an idle timeout (idleTimeout=0), then later calling SetIdleTimeout to enable it — e.g. reacting to a runtime config change in a service wrapping pools.

Common situations: Hot-reloading timeouts from a config file where the new config enables an idle timeout that wasn't set at startup; test code (TestIdleTimeout) versus production config drift; copying pool setup code that drops the timeout parameter.

Understand the failure class

Related errors


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