From ab4327a0190ae1c40d6a5d3f562a7f2af1e841e7 Mon Sep 17 00:00:00 2001 From: Frediano Ziglio Date: Sun, 2 May 2021 20:15:00 +0100 Subject: [PATCH] Avoid having 2 goroutines to handle file changes On goroutine was just waiting the goroutine handing the events just to keep the "watcher" variable alive. Move defer to event handling goroutine and avoid one goroutine. Signed-off-by: Frediano Ziglio --- viper.go | 110 +++++++++++++++++++++++++------------------------------ 1 file changed, 49 insertions(+), 61 deletions(-) diff --git a/viper.go b/viper.go index 3c8ee2c..e66f1cc 100644 --- a/viper.go +++ b/viper.go @@ -32,7 +32,6 @@ import ( "reflect" "strconv" "strings" - "sync" "time" "github.com/fsnotify/fsnotify" @@ -341,72 +340,61 @@ func (v *Viper) OnConfigChange(run func(in fsnotify.Event)) { func WatchConfig() { v.WatchConfig() } func (v *Viper) WatchConfig() { - initWG := sync.WaitGroup{} - initWG.Add(1) + // we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way + filename, err := v.getConfigFile() + if err != nil { + log.Printf("error: %v\n", err) + return + } + + configFile := filepath.Clean(filename) + configDir, _ := filepath.Split(configFile) + realConfigFile, _ := filepath.EvalSymlinks(filename) + + watcher, err := newWatcher() + if err != nil { + log.Fatal(err) + return + } + go func() { - watcher, err := newWatcher() - if err != nil { - log.Fatal(err) - } defer watcher.Close() - // we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way - filename, err := v.getConfigFile() - if err != nil { - log.Printf("error: %v\n", err) - initWG.Done() - return - } - - configFile := filepath.Clean(filename) - configDir, _ := filepath.Split(configFile) - realConfigFile, _ := filepath.EvalSymlinks(filename) - - eventsWG := sync.WaitGroup{} - eventsWG.Add(1) - go func() { - for { - select { - case event, ok := <-watcher.Events: - if !ok { // 'Events' channel is closed - eventsWG.Done() - return - } - currentConfigFile, _ := filepath.EvalSymlinks(filename) - // we only care about the config file with the following cases: - // 1 - if the config file was modified or created - // 2 - if the real path to the config file changed (eg: k8s ConfigMap replacement) - const writeOrCreateMask = fsnotify.Write | fsnotify.Create - if (filepath.Clean(event.Name) == configFile && - event.Op&writeOrCreateMask != 0) || - (currentConfigFile != "" && currentConfigFile != realConfigFile) { - realConfigFile = currentConfigFile - err := v.ReadInConfig() - if err != nil { - log.Printf("error reading config file: %v\n", err) - } - if v.onConfigChange != nil { - v.onConfigChange(event) - } - } else if filepath.Clean(event.Name) == configFile && - event.Op&fsnotify.Remove != 0 { - eventsWG.Done() - return - } - - case err, ok := <-watcher.Errors: - if ok { // 'Errors' channel is not closed - log.Printf("watcher error: %v\n", err) - } - eventsWG.Done() + for { + select { + case event, ok := <-watcher.Events: + if !ok { // 'Events' channel is closed return } + currentConfigFile, _ := filepath.EvalSymlinks(filename) + // we only care about the config file with the following cases: + // 1 - if the config file was modified or created + // 2 - if the real path to the config file changed (eg: k8s ConfigMap replacement) + const writeOrCreateMask = fsnotify.Write | fsnotify.Create + if (filepath.Clean(event.Name) == configFile && + event.Op&writeOrCreateMask != 0) || + (currentConfigFile != "" && currentConfigFile != realConfigFile) { + realConfigFile = currentConfigFile + err := v.ReadInConfig() + if err != nil { + log.Printf("error reading config file: %v\n", err) + } + if v.onConfigChange != nil { + v.onConfigChange(event) + } + } else if filepath.Clean(event.Name) == configFile && + event.Op&fsnotify.Remove != 0 { + return + } + + case err, ok := <-watcher.Errors: + if ok { // 'Errors' channel is not closed + log.Printf("watcher error: %v\n", err) + } + return } - }() - watcher.Add(configDir) - initWG.Done() // done initializing the watch in this go routine, so the parent routine can move on... - eventsWG.Wait() // now, wait for event loop to end in this go-routine... + } }() - initWG.Wait() // make sure that the go routine above fully ended before returning + watcher.Add(configDir) } // SetConfigFile explicitly defines the path, name and extension of the config file.