-
Notifications
You must be signed in to change notification settings - Fork 2k
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
There might be some bugs when using Set() to write. These errors often occur when the file does not exist and during operations after the file is created for the first time. #1934
Comments
Of course, if the project is not very sensitive to performance requirements, you can use this temporary solution like I did. Although this is how I solved the problem, I don’t think it’s the best solution. package main
import (
"fmt"
"log"
"time"
"github.com/spf13/viper"
)
var v *viper.Viper
func main() {
initViperConfig()
initViperConfigByNew()
viperSet("example.a", "123")
viperSet("example.b", "123")
viperSet("example.c", "123")
vSet("example.a", "123")
vSet("example.b", "123")
vSet("example.c", "123")
}
// 直接使用 viper 初始化对其应的配置文件, 供后续验证使用
func initViperConfig() {
viper.SetConfigName("config")
viper.SetConfigType("json")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// 配置文件不存在,创建默认配置
defaultConfig := map[string]interface{}{
// 在这里添加默认配置项
"example_key": "example_value",
}
for key, value := range defaultConfig {
viper.SetDefault(key, value)
}
err = viper.SafeWriteConfig()
if err != nil {
log.Printf("创建配置文件失败: %s\n", err)
} else {
log.Println("已创建默认配置文件")
}
} else {
log.Printf("读取配置文件失败: %s\n", err)
}
}
viper.WatchConfig()
}
// 使用 viper.New() 创建的实例, 初始化对应的配置文件, 供后续验证使用
func initViperConfigByNew() {
v = nil
v = viper.New()
v.SetConfigName("configNew")
v.SetConfigType("json")
v.AddConfigPath(".")
err := v.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// 配置文件不存在,创建默认配置
defaultConfig := map[string]interface{}{
// 在这里添加默认配置项
"example_key": "example_value",
}
for key, value := range defaultConfig {
v.SetDefault(key, value)
}
err = v.SafeWriteConfig()
if err != nil {
log.Printf("创建配置文件失败: %s\n", err)
} else {
log.Println("已创建默认配置文件")
}
} else {
log.Printf("读取配置文件失败: %s\n", err)
}
}
v.WatchConfig()
}
func viperSet(key string, value interface{}) {
viper.Set(key, value)
if err := viper.WriteConfig(); err != nil {
fmt.Println("向config.json保存配置时发生致命错误", "err", err.Error())
}
viper.Set(key, nil)
// 等待 viper.WatchConfig 监听真实配置
time.Sleep(time.Millisecond * 100)
}
func vSet(key string, value interface{}) {
v.Set(key, value)
if err := v.WriteConfig(); err != nil {
fmt.Println("向configNew.json保存配置时发生致命错误", "err", err.Error())
}
v.Set(key, nil)
// 等待 v.WatchConfig 监听真实配置
time.Sleep(time.Millisecond * 100)
} |
This is my latest temporary solution, which can minimize any unnecessary waiting time during write operations. It also introduces a protective maximum wait time exit mechanism to avoid the occurrence of permanent waiting caused by actual
package main
import (
"fmt"
"log"
"time"
"github.com/spf13/viper"
)
var v *viper.Viper
func main() {
initViperConfig()
initViperConfigByNew()
viperSet("example.a", "123")
viperSet("example.b", "123")
viperSet("example.c", "123")
viperSet("example.c", nil) // 虽然chatGPT告诉我可以通过此方式来删除一个key-value, 但此处并未成功, 本以为是受到了WatchConfig的影响, 但随后发现并不是, 不过这个属于另一个问题的讨论范畴了, 并不影响此处的示例。
viperSet("example.d", nil)
vSet("example.a", "123")
vSet("example.b", "123")
vSet("example.c", "123")
vSet("example.c", nil) // 虽然chatGPT告诉我可以通过此方式来删除一个key-value, 但此处并未成功, 本以为是受到了WatchConfig的影响, 但随后发现并不是, 不过这个属于另一个问题的讨论范畴了, 并不影响此处的示例。
vSet("example.d", nil)
for {
}
}
// 直接使用 viper 初始化对其应的配置文件, 供后续验证使用
func initViperConfig() {
viper.SetConfigName("config")
viper.SetConfigType("json")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// 配置文件不存在,创建默认配置
defaultConfig := map[string]interface{}{
// 在这里添加默认配置项
"example_key": "example_value",
}
for key, value := range defaultConfig {
viper.SetDefault(key, value)
}
err = viper.SafeWriteConfig()
if err != nil {
log.Printf("创建配置文件失败: %s\n", err)
} else {
log.Println("已创建默认配置文件")
}
} else {
log.Printf("读取配置文件失败: %s\n", err)
}
}
viper.WatchConfig()
}
// 使用 viper.New() 创建的实例, 初始化对应的配置文件, 供后续验证使用
func initViperConfigByNew() {
v = nil
v = viper.New()
v.SetConfigName("configNew")
v.SetConfigType("json")
v.AddConfigPath(".")
err := v.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// 配置文件不存在,创建默认配置
defaultConfig := map[string]interface{}{
// 在这里添加默认配置项
"example_key": "example_value",
}
for key, value := range defaultConfig {
v.SetDefault(key, value)
}
err = v.SafeWriteConfig()
if err != nil {
log.Printf("创建配置文件失败: %s\n", err)
} else {
log.Println("已创建默认配置文件")
}
} else {
log.Printf("读取配置文件失败: %s\n", err)
}
}
v.WatchConfig()
}
func viperSet(key string, value interface{}) {
viper.Set(key, value)
if err := viper.WriteConfig(); err != nil {
fmt.Println("向config.json保存配置时发生致命错误", "err", err.Error())
}
viper.Set(key, nil)
// 等待 viper.WatchConfig 监听真实配置
sleep := true
ch := make(chan (struct{}))
defer close(ch)
go func(sleep *bool, ch chan struct{}) {
defer fmt.Println("保护功能完成, 退出当前goroutine以结束保护---config.json")
for {
select {
case <-ch:
fmt.Println("符合预期的退出行为---config.json")
return
case <-time.After(time.Millisecond * 100): // 这个最大退出时间, 由您自由指定
fmt.Println("到达等待时间上限, 而进行的自动强制退出行为, 以避免资源浪费式的长期甚至永久等待行为---config.json")
*sleep = false
return
}
}
}(&sleep, ch)
for sleep {
if viper.Get(key) != nil {
sleep = false
// 如果函数结束, 通道自然会关闭, 从而解除阻塞行为。无需使用下行中可能引入新阻塞的逻辑。
// ch <- struct{}{}
} else {
fmt.Println("阻止了config.json中一次可能存在的错误删除行为")
}
}
}
func vSet(key string, value interface{}) {
v.Set(key, value)
if err := v.WriteConfig(); err != nil {
fmt.Println("向configNew.json保存配置时发生致命错误", "err", err.Error())
}
v.Set(key, nil)
// 等待 v.WatchConfig 监听真实配置
sleep := true
ch := make(chan (struct{}))
defer close(ch)
go func(sleep *bool, ch chan struct{}) {
defer fmt.Println("保护功能完成, 退出当前goroutine以结束保护---configNew.json")
for {
select {
case <-ch:
fmt.Println("符合预期的退出行为---configNew.json")
return
case <-time.After(time.Millisecond * 100): // 这个最大退出时间, 由您自由指定
fmt.Println("到达等待时间上限, 而进行的自动强制退出行为, 以避免资源浪费式的长期甚至永久等待行为---configNew.json")
*sleep = false
return
}
}
}(&sleep, ch)
for sleep {
if v.Get(key) != nil {
sleep = false
// 如果函数结束, 通道自然会关闭, 从而解除阻塞行为。无需使用下行中可能引入新阻塞的逻辑。
// ch <- struct{}{}
} else {
fmt.Println("阻止了configNew.json中一次可能存在的错误删除行为")
}
}
} |
|
Preflight Checklist
Viper Version
1.19.0
Go Version
1.22.0
Config Source
Files
Format
JSON
Repl.it link
No response
Code reproducing the issue
Expected Behavior
After executing the above code, the corresponding configuration file will be generated (if there is already a corresponding configuration file, please delete it before executing the code), and it should directly contain the following content:
Actual Behavior
If no issues are found, please delete the corresponding configuration file and execute it several more times, as sometimes it may only contain part of the content (missing one or two items), such as:
Steps To Reproduce
Additional Information
It seems that during the Set() process, other content at the same level was accidentally overwritten or deleted, or after setting to nil, it was not immediately detected, resulting in the corresponding key being incorrectly deleted during the next file write. In short, I don’t quite understand the working principles of Viper, but the above-mentioned bug phenomenon does exist.
The text was updated successfully, but these errors were encountered: