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

  1. Set a non-zero idleTimeout when creating the ResourcePool (NewResourcePool ... idleTimeout), then call SetIdleTimeout at runtime
  2. Guard the call: only invoke SetIdleTimeout if an idle timeout was configured at creation
  3. 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

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

Related errors


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