Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Concurrency-safe way to use viper.WatchConfig()? #378

Open
libreoscar opened this issue Aug 16, 2017 · 8 comments
Open

Concurrency-safe way to use viper.WatchConfig()? #378

libreoscar opened this issue Aug 16, 2017 · 8 comments

Comments

@libreoscar
Copy link

viper.WatchConfig() store the changed config without any synchronization. Won't it cause data race if there are concurrent config reads in other threads/goroutines?

@ghost
Copy link

ghost commented Oct 9, 2017

Yes, there is indeed a data race as I just encountered. Take this for example:

viper.OnConfigChange(func(e fsnotify.Event) {
	if viper.IsSet("globalcd") {
		dur, err := time.ParseDuration(viper.GetString("globalcd"))
		if err != nil {
			log.Printf("Error parsing globalcd value: %s", err)
			return
		}

		if dur > time.Second {
			globalCooldown.Length = dur
		} else {
			globalCooldown.Length = time.Second * 60
		}
	}
}

Race condition on the data read of that value intermittently. So sometimes a value such as: 60s will be read in as 60 which is an error condition for time.ParseDuration as it's lacking the time unit. (exact error: time: missing unit in duration 60)

aeneasr added a commit to ory/viper that referenced this issue Jul 15, 2019
aeneasr added a commit to ory/viper that referenced this issue Jul 15, 2019
ttys3 pushed a commit to ttys3/viper that referenced this issue Aug 25, 2019
@7d4b9
Copy link

7d4b9 commented Jan 26, 2020

The function registered with OnConfigChange is called in the goroutine of the WatchConfig function event loop and this is a separate goroutine from the one you are using to call viper.

You should not access the viper instance from this registered function as far as viper is not safe for concurrent use.

You should use an external mutex to synchronize you with yourself when you use your viper instance if you want to do that.

package main

import (
	"sync"
	"time"

	"github.com/spf13/viper"
	"github.com/fsnotify/fsnotify"
)

var viperLock sync.Mutex

func main() {

	viperLock.Lock()
	viper.OnConfigChange(func(in fsnotify.Event) {
		viperLock.Lock()
		viper.Set("delay", "10s")
		viperLock.Unlock()
	})
	viperLock.Unlock()

	viperLock.Lock()
	viper.Set("delay", "30s")
	viperLock.Unlock()

	// ...

	viperLock.Lock()
	d := viper.GetDuration("delay")
	viperLock.Unlock()

	time.Sleep(d)
}

@cfergeau
Copy link

cfergeau commented Feb 2, 2021

The function registered with OnConfigChange is called in the goroutine of the WatchConfig function event loop and this is a separate goroutine from the one you are using to call viper.

As I understand it, it's not OnConfigChange which is the problem, but the implementation of viper.WatchConfig:

func (v *Viper) WatchConfig() {
	...
	go func() {
						...
						err := v.ReadInConfig()
						if err != nil {
							log.Printf("error reading config file: %v\n", err)
						}
						if v.onConfigChange != nil {
							v.onConfigChange(event)
						}
...

The v.ReadConfig happens in its own go routine, but can't be synchronized with concurrent uses of v

@Valdenirmezadri
Copy link

Hello, i'm make changes to safe concurrency and work for me

https://github.com/Valdenirmezadri/viper

@ArpithaRao
Copy link

ArpithaRao commented Aug 25, 2021

I am have the same problem. in file viper.go, at line 390:
if v.onConfigChange != nil {
v.onConfigChange(event)
}
I see data race for Read. And at line 341 in the same file, I see data race for write.
Is there a solution for this? This only occurs for the very first time config dynamic change.

@simonkotwicz
Copy link

@sagikazarmark thoughts?

@qazwsxedckll
Copy link

func (v *Viper) WatchConfig() {
	go func() {
		go func() {
			for {
				select {
				case event, ok := <-watcher.Events:
					if (filepath.Clean(event.Name) == configFile &&
						err := v.ReadInConfig()
						if v.onConfigChange != nil {
							v.onConfigChange(event)
						}
					} else if filepath.Clean(event.Name) == configFile && event.Has(fsnotify.Remove) {
						eventsWG.Done()
						return
					}
				case err, ok := <-watcher.Errors:
				}
			}
		}()
	}()
}


func (v *Viper) ReadInConfig() error {
	config := make(map[string]interface{})

	err = v.unmarshalReader(bytes.NewReader(file), config)
	if err != nil {
		return err
	}

	v.config = config
	return nil
}

This is a race condition to assign v.config and to write configs in onConfigChange without synchronization. No one is looking at this obvious problem?

@zetaab
Copy link

zetaab commented Sep 17, 2023

there are multiple issues for this race condition in watchConfig. Seems that none cares about the issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants