Open lyondhill opened 7 years ago
Not sure what you use Viper for, but the common use case is to use it for configuration -- and to set up that in one thread before the real application starts.
If we should do something on the Viper side it would be to force people into this init -> use by removing all the setters and take all in via constructor funcs.
the issue Im reporting is that viper doesn't handle concurrency. Regardless of my use case it is an issue, which could be easily solved with a mutex (https://golang.org/pkg/sync/#Mutex).
If you intend to force users into 'the common use case' your tool just becomes less useful.
I do not intend to do anything about this, I was just suggesting.This is a @spf13 project, he will probably decide if it is worth the extra work/tests to sprinkle the project with mutex locking to support a rare use case.
bump - @lyondhill , do you have a test case you can share? Thanks.
I no longer have the code that I was using that would make it happen. But it is very easy to force to happen. The problem really lies in that viper is updating a map in a non concurrent safe way. This triggers it every time for me:
package main
import "github.com/spf13/viper"
func main() {
go setDefaults()
setDefaults()
}
func setDefaults() {
for {
viper.SetDefault("thing", "value")
}
}
I agree. I believe basic getters/setters should be thread-safe. There are many uses cases where you'd need to grab configs/values during post-boot runtime of your app (e.g. queues, buffers, etc...)
you'd need to grab configs/values during post-boot runtime of your app (e.g. queues, buffers, etc...)
Grab as in "get", I assume. But what are the use cases for concurrently read/write?
Viper is not thread-safe. Using some typical Go techniques, I re-implemented a small core of Viper with JSON support and support for basic Consul watchers (not the most ideal implementation, since it uses polling.) You can find the small sample rewrite here: github.com/rbastic/mvga
FYI, my use case is for microservices requiring dynamic configuration reloading.
Without proper locking, using any of the watch functions is unsafe. This includes WatchConfig and WatchRemoteConfigOnChannel.
Got this race and that was unexpected... Maybe we need to make a mark that bind it's not for concurrent use?
Got same problem, but race appears in a place, which is totally unexpected to face it: UnmarshalKey
. My expectation was, that unmarshaling part of config into external struct, should not change any internal structures inside viper package.
Here is small test, which fails for me in 50% of cases:
package main
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
"github.com/spf13/afero"
"github.com/spf13/viper"
)
type Setting struct {
TestValue *string `json:"testValue,omitempty" mapstructure:"testvalue"`
}
func TestConcurrency(t *testing.T) {
content := []byte(`{"settings": {"A": {"value": "value A"}, "B": {"value": "value B"}}}`)
aferoFS := afero.NewMemMapFs()
require.NoError(t, afero.WriteFile(aferoFS, "conf.json", content, 0755))
v := viper.New()
v.SetConfigType("json")
v.SetFs(aferoFS)
v.SetConfigFile("conf.json")
require.NoError(t, v.ReadInConfig())
for i := 1; i < 100; i++ {
t.Run(fmt.Sprintf("flow #%d", i), func(tt *testing.T) {
tt.Parallel()
settings := make(map[string]*Setting)
require.NotPanics(t, func() {
require.NoError(t, v.UnmarshalKey("settings", &settings))
})
})
}
}
In my case problem is with insensitiviseMaps
call from UnmarshalKey
( https://github.com/spf13/viper/blob/master/viper.go#L1582 )
Is this fixed by https://github.com/spf13/viper/commit/9e56dacc08fbbf8c9ee2dbc717553c758ce42bc9 that was committed yesterday?
I use viper in production and this do kill me.
Got bit by this today, using viper.Set
. This really should be noted in the documentation. And then it should be fixed by using a sync.RWMutex
everywhere the internal map is accessed.
Agree docs should be updated to note it's not thread-safe. However, it doesn't necessarily mean viper should be thread-safe as that does come with a performance cost. You can always implement a wrapper around viper of you want concurrent access.
IIRC the main problem was with reloading config on changes. So on reload, viper writes new option values to the internal map without any locks and does it in a separate goroutine. If anyone tries to read an option from this map (and this happens obviously in another goroutine) at the same time then we will get a concurrent access error. This problem cannot be solved with a kind of external wrapper with its own locks. As a result, this feature of viper is not completely usable.
IIRC the main problem was with reloading config on changes.
Doing concurrent Set
s, or a Get*
while Set
ing is also a problem, and one that is not documented.
I get this error periodically
Since the update in go to the map concurrent write check viper occasionally fails
The line numbers no longer match the current master branch but the issue for this specific one would be found here now: https://github.com/spf13/viper/blob/master/viper.go#L1052