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 <fziglio@cyral.com>
This commit is contained in:
Frediano Ziglio 2021-05-02 20:15:00 +01:00
parent 24f5069098
commit ab4327a019

110
viper.go
View file

@ -32,7 +32,6 @@ import (
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
@ -341,72 +340,61 @@ func (v *Viper) OnConfigChange(run func(in fsnotify.Event)) {
func WatchConfig() { v.WatchConfig() } func WatchConfig() { v.WatchConfig() }
func (v *Viper) WatchConfig() { func (v *Viper) WatchConfig() {
initWG := sync.WaitGroup{} // we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way
initWG.Add(1) 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() { go func() {
watcher, err := newWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close() defer watcher.Close()
// we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way for {
filename, err := v.getConfigFile() select {
if err != nil { case event, ok := <-watcher.Events:
log.Printf("error: %v\n", err) if !ok { // 'Events' channel is closed
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()
return 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. // SetConfigFile explicitly defines the path, name and extension of the config file.