go-micro v4.11.1 - Config Close Releases Watchers


go-micro v4.11.1 was published on 10 September 2026. Config.Close now closes the underlying config loader and releases the file watchers and open handles that loader held. The patch also avoids a panic on a nil error when watchers stop, and it serializes extra Config.Close calls so cleanup runs once.

The full release notes and downloads are on the GitHub release page. The tag is a stable patch on the 4.11 line. All three items are fixes for issue 4913. The page documents config shutdown for this tag.

Config.Close is how callers shut a config object down. The defect was that the method could return while the loader under it stayed alive. That loader owns the file watchers and the open handles. A caller that invoked Config.Close and then dropped the config value could leave both in the process until exit.

One config object held for the process lifetime leaks little, because exit reclaims its watches. The leak shows up when config is constructed again. A worker that reloads a file source, a test process that builds config per case, or a control loop that rebuilds a service object on each pass keeps the old watches if Close never reaches the loader. Watchers and descriptors then accumulate on every rebuild.

Linux caps both resources. Hitting the user watch limit or the process open file limit fails a later open or a later watch, often in code that never mentions config. The release notes leave the watcher implementation unnamed. They name the resources Config.Close must drop: file watchers and open handles. In v4.11.1 that drop happens because Config.Close closes the loader.

Code that called Config.Close and then kept using the loader is the path to retest. This patch defines close as closing that loader. It ships as patch v4.11.1, and the notes list no migration steps, so the previous behavior stands as a defect. Wrappers that close config and still read through the same loader can now observe a loader that is already gone. Callers that use the config API and discard the value after Config.Close match the fixed contract. On a pipeline worker, the reload check is concrete: open config, close it, confirm the watch count and the handle count return to the baseline, then open it again.

Stopping config watchers could panic when the error value was nil. The GitHub release page records that outcome without a stack or the branch that produced the nil error.

Watcher stop runs when config shuts down and when a reload drops the previous watch. A panic there sits on the teardown path. An unrecovered panic aborts the process while it is trying to exit, so a service manager records a crash. A batch worker can fail the task on the way out, after the job had already decided to stop. The log line is a panic tied to a nil error, so the first look is the stop path itself.

A nil error panic in Go often comes from an error interface that is non nil while the concrete pointer inside it is nil. Formatting or wrapping that value panics. The notes leave that mechanism unconfirmed for issue 4913. The contract they publish is direct: stopping config watchers must tolerate a nil error.

The fix stays inside the watcher stop path. A process that panicked while stopping watchers is the one this line item changes. A process whose stop path was already quiet keeps that quiet exit.

Repeated Config.Close calls are serialized, and cleanup runs once.

Two closes on one config value are normal shutdown structure. A defer calls Config.Close, and an error branch calls it as well. Separate goroutines can both observe cancellation and both close. If both run the release logic, the second pass stops watchers and closes handles that the first pass already released. Those objects are already in the closed state. Running the release logic again is what this fix removes.

The notes leave the previously double closed object unnamed. The rule they specify is the one operators can test: serialize the calls, run cleanup once. The second Config.Close does not start another teardown. Whether that second call blocks until the first cleanup finishes, or returns once cleanup is already done, is unspecified.

Config.Close can therefore wait on another Config.Close. A caller that holds a lock which loader teardown also needs can stall both sides. Call Close without holding locks the loader may need while it drops watches and handles.

Shutdown code can call Config.Close from more than one site after this patch. A private flag whose only job was to remember that cleanup had already run is no longer required for a single cleanup. Issue 4913 tracks the loader close, the nil error panic, and the single cleanup as one fix set. A watcher that survives Close, a panic on the way out, and a second cleanup of the same loader are the symptoms this tag addresses.