Migration: config observer signature (channel β returned error)¶
The config hot-reload rework replaces the container's unbuffered, unread error channel with a returned-error contract. This removes the deadlock class that the channel caused and is the idiomatic Go shape. The change is a one-line update at every observer call site.
This is a breaking change to the config.Observable interface and the
AddObserverFunc signature. GTB is pre-1.0, so this ships as a minor bump
with no compatibility shim.
Breaking Changes¶
config.Observable / AddObserverFunc now return an error¶
Package: pkg/config
Before:
// Observable interface
type Observable interface {
Run(Containable, chan error)
}
// Function observer
cfg.AddObserverFunc(func(c config.Containable, errs chan error) {
if err := apply(c); err != nil {
errs <- err
return
}
})
After:
// Observable interface
type Observable interface {
Run(Containable) error
}
// Function observer
cfg.AddObserverFunc(func(c config.Containable) error {
return apply(c)
})
Migration:
- Change every observer's
Run(Containable, chan error)toRun(Containable) error. - Replace
errs <- err; returnwithreturn err. - Replace early
return(no error) withreturn nil. - In tests, replace
observer.Run(cfg, errCh)witherr := observer.Run(cfg)and assert on the returned error directly.
The framework logs any returned error. An observer error no longer blocks subsequent observers or stalls future reloads.
New Features¶
The same rework also brings, with no further action required:
- Multi-file merge is preserved on reload β all configured files are re-read and re-merged on change (previously only the last file was watched and the merge was discarded).
- Candidate-validate-swap β an invalid reload is rejected and the
last-known-good config is retained;
Get*never serves an invalid or half-merged config (fail-closed). - Single-file containers are watched (previously only multi-file ones were).
- Configurable debounce via
config.WithReloadDebounce(d)(default 250 ms). Container.Close()to stop the watcher and release its OS resources.
Learning about FAILED reloads¶
Under the previous contract, an observer received a chan error and could be
handed an error on a failed reload. With the returned-error contract, observers
return errors β there is no channel to push a reload-time error to them, and
observers are only ever called for a reload that succeeded.
To react to a rejected reload (a fail-closed parse/merge error, a missing
primary file, or a schema-validation failure β all of which retain
last-known-good), register an OnReloadError callback. This is the supported
replacement for the old "notify observers of the error" behaviour:
container.OnReloadError(func(err error) {
log.Warn("config reload rejected; keeping last-known-good", "error", err)
})
OnReloadError is additive to the container's own ERROR log, never fires for
a successful reload (observers handle that), and follows the same race-safe,
deadlock-free locking discipline as observer notification. See
Reacting to rejected reloads.