diff --git a/config.go b/config.go index aa1b068..f0488fe 100644 --- a/config.go +++ b/config.go @@ -34,9 +34,9 @@ type Config struct { onStatus func(loader Loader, changed bool, err error) converter *convert.Converter - providers providers - onChanges onChanges - watchProvider atomic.Pointer[func(*provider)] + providers providers + onChanges onChanges + watched atomic.Pointer[func(*provider)] } // New creates a new Config with the given Option(s). @@ -84,7 +84,7 @@ func (c *Config) Load(loader Loader) error { return nil } - // Special handling if loader is also watcher. + // Register status callback if the loader is a Statuser. if statuser, ok := loader.(Statuser); ok { statuser.Status(func(changed bool, err error) { if err != nil { @@ -101,7 +101,9 @@ func (c *Config) Load(loader Loader) error { }) } - if watch := c.watchProvider.Load(); watch != nil { + // Register watch callback if the loader is a Watcher and the watch is started. + // While Config.Watch is called, c.watched is set for registering the watch callback. + if watch := c.watched.Load(); watch != nil { (*watch)(provider) } @@ -286,8 +288,10 @@ func (p *providers) traverse(action func(*provider)) { } func (p *providers) sub(path []string) any { - p.mutex.RLock() - defer p.mutex.RUnlock() + // Here does not need lock since p.values is atomic pointer. + // The map of configuration is just swapping in and out, + // but the map itself is immutable. + // So unmarshal isn't blocked by Config.Load or updating changes by Watch. val := p.values.Load() if val == nil { // To support zero Config diff --git a/watch.go b/watch.go index 080f14f..9081d2f 100644 --- a/watch.go +++ b/watch.go @@ -59,7 +59,9 @@ func (c *Config) Watch(ctx context.Context) error { //nolint:cyclop,funlen,gocog } } - if !c.watchProvider.CompareAndSwap(nil, &watchProvider) { + // Set c.watched so that the loader loaded after Watch can register the watch callback. + // It's also used for marker that Watch has been called. + if !c.watched.CompareAndSwap(nil, &watchProvider) { c.log(ctx, slog.LevelWarn, "Config has been watched, call Watch more than once has no effects.") return nil