Preflight Checklist
- [x] I have searched the issue tracker for an issue that matches the one I want to file, without success.
- [x] I am not looking for support or already pursued the available support channels without success.
- [x] I have checked the troubleshooting guide for my problem, without success.
Viper Version
1.17.0
Go Version
1.23.9
Config Source
Remove K/V stores
Format
YAML
Repl.it link
No response
Code reproducing the issue
func initViperRemote() {
err := viper.AddRemoteProvider("etcd3",
"http://127.0.0.1:12379", "webook")
if err != nil {
panic(err)
}
viper.SetConfigType("yaml")
viper.OnConfigChange(func(in fsnotify.Event) {
log.Println("远程配置中心发生变更")
})
go func() {
for {
err = viper.WatchRemoteConfig()
if err != nil {
panic(err)
}
log.Println("watch", viper.GetString("test.key"))
}
}()
err = viper.ReadRemoteConfig()
if err != nil {
panic(err)
}
}
Expected Behavior
The expectation is that it should work normally, and when the remote configuration changes, the local configuration will also be updated
Actual Behavior
In fact, when the program is initialized, an error is reported directly. There is a problem of concurrent map writing
Steps To Reproduce
Run it several times
Additional Information
No response
Comment From: spacez320
@pingxianga I tried reproducing this with your example above, but could not.
In fact, when the program is initialized, an error is reported directly.
I agree there is some race condition here, but I'm not getting an error after several executions. What actually happens for me is sometimes I get the value, sometimes the program just ends, which is what I'd expect given that either the Goroutine or calling function will execute before one-another non-deterministically.
What error are you seeing, exactly? Are you able to change the code above to get it to behave like this reproduce-ably?
Comment From: supercooltest2024
Found an unrelated fatal error while attempting to reproduce above (uses WatchConfig instead of WatchRemoteConfig (which apparently behaves differently?)).
package main
import (
"github.com/spf13/viper"
)
func main() {
viper.AddConfigPath(".")
for {
viper.WatchConfig()
}
}
First time I've seen a golang crash with register values in it: (it should probably panic/no-op as soon as WatchConfig is called a second time, not 10 seconds later after having been called uncountably many times)
r0 0x0
r1 0x0
r2 0x0
r3 0x0
...
lr 0x192dfbc28
sp 0x16f842bf0
pc 0x192dc4744
fault 0x192dc4744
exit status 2
I think for the OP, from the title, that the error is concurrent map writes likely in etcd3 remote provider's watch implementation. With any race condition, a nice way to make it happen more often is with more threads running infinite loops
func initViperRemote() {
err := viper.AddRemoteProvider("etcd3",
"http://127.0.0.1:12379", "webook")
if err != nil {
panic(err)
}
viper.SetConfigType("yaml")
viper.OnConfigChange(func(in fsnotify.Event) {
log.Println("远程配置中心发生变更") // The remote configuration center has changed
})
go func() {
// This is only executed once, as WatchRemoteConfig blocks until an error, then this panics
// for {
err = viper.WatchRemoteConfig()
if err != nil {
panic(err)
}
// This is unreachable(?), as WatchRemoteConfig blocks forever until an error occurs
// log.Println("watch", viper.GetString("test.key"))
// }
}()
// New: forever loop to attempt to read a changing map value
for {
err = viper.ReadRemoteConfig() // or some other function that reads the remote config
if err != nil {
panic(err)
}
}
}
As well as another script which continually changes the config
# if etcd3 were a filesystem
import time
while True:
open("config.yaml", "w").write("test:\n key: a"+str(time.time()))
I can't test myself as I don't have etcd3 setup.