{"record":{"id":"2a094431f2279caa","repo":"vitessio/vitess","slug":"setidletimeout-called-when-timer-not-initialized","errorCode":null,"errorMessage":"SetIdleTimeout called when timer not initialized","messagePattern":"SetIdleTimeout called when timer not initialized","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/pools/resource_pool.go","lineNumber":346,"sourceCode":"\tif capacity == 0 {\n\t\tclose(rp.resources)\n\t}\n\treturn nil\n}\n\nfunc (rp *ResourcePool) recordWait(start time.Time) {\n\trp.waitCount.Add(1)\n\trp.waitTime.Add(time.Since(start).Nanoseconds())\n\tif rp.logWait != nil {\n\t\trp.logWait(start)\n\t}\n}\n\n// SetIdleTimeout sets the idle timeout. It can only be used if there was an\n// idle timeout set when the pool was created.\nfunc (rp *ResourcePool) SetIdleTimeout(idleTimeout time.Duration) {\n\tif rp.idleTimer == nil {\n\t\tpanic(\"SetIdleTimeout called when timer not initialized\")\n\t}\n\n\trp.idleTimeout.Store(idleTimeout.Nanoseconds())\n\trp.idleTimer.SetInterval(idleTimeout / 10)\n}\n\n// StatsJSON returns the stats in JSON format.\nfunc (rp *ResourcePool) StatsJSON() string {\n\treturn fmt.Sprintf(`{\"Capacity\": %v, \"Available\": %v, \"Active\": %v, \"InUse\": %v, \"MaxCapacity\": %v, \"WaitCount\": %v, \"WaitTime\": %v, \"IdleTimeout\": %v, \"IdleClosed\": %v, \"MaxLifetimeClosed\": %v, \"Exhausted\": %v}`,\n\t\trp.Capacity(),\n\t\trp.Available(),\n\t\trp.Active(),\n\t\trp.InUse(),\n\t\trp.MaxCap(),\n\t\trp.WaitCount(),\n\t\trp.WaitTime().Nanoseconds(),\n\t\trp.IdleTimeout().Nanoseconds(),\n\t\trp.IdleClosed(),","sourceCodeStart":328,"sourceCodeEnd":364,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/pools/resource_pool.go#L328-L364","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Set a nonzero IdleTimeout when constructing the ResourcePool so idleTimer is initialized.","Recreate the pool with the new idle timeout instead of mutating it at runtime.","Guard calls: only invoke SetIdleTimeout when the pool was created with an idle timeout."],"exampleFix":"// before\npool := pools.NewResourcePool(f, cap, cap, 0) // no idle timeout\npool.SetIdleTimeout(30 * time.Second) // panics\n// after\npool := pools.NewResourcePool(f, cap, cap, 30*time.Second) // idle timeout set at creation\npool.SetIdleTimeout(60 * time.Second) // OK","handlingStrategy":"validation","validationCode":"if rp.IDleTimeout == 0 { // pool created without idle timeout\n    return errors.New(\"SetIdleTimeout requires a pool created with an idle timeout\")\n}","typeGuard":null,"tryCatchPattern":"defer func() { if r := recover(); r != nil { err = fmt.Errorf(\"SetIdleTimeout failed: %v\", r) } }()","preventionTips":["Always pass a nonzero IdleTimeout to NewResourcePool if runtime changes are planned.","Recreate the pool rather than toggling idle timeout from zero.","Gate config-driven SetIdleTimeout calls on the original pool config."],"tags":["pools","panic","resource-management","configuration"],"backgroundTag":"idle-timeout-not-initialized","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}