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

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

Open
3 tasks done
LuSrackhall opened this issue Oct 7, 2024 · 3 comments
Labels
kind/bug Something isn't working

Comments

@LuSrackhall
Copy link

LuSrackhall commented Oct 7, 2024

Preflight Checklist

  • I have searched the issue tracker for an issue that matches the one I want to file, without success.
  • I am not looking for support or already pursued the available support channels without success.
  • I have checked the troubleshooting guide for my problem, without success.

Viper Version

1.19.0

Go Version

1.22.0

Config Source

Files

Format

JSON

Repl.it link

No response

Code reproducing the issue

package main

import (
	"fmt"
	"log"

	"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)
}

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)
}

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:

{
  "example": {
    "a": "123",
    "b": "123",
    "c": "123"
  },
  "example_key": "example_value"
}

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:

{
  "example": {
    "b": "123",
    "c": "123"
  },
  "example_key": "example_value"
}

Steps To Reproduce

  1. Ensure the configuration file is newly generated when the code is executed (if this is not the first execution, please delete the previously generated corresponding configuration file).
  2. Execute the code.

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.

@LuSrackhall LuSrackhall added the kind/bug Something isn't working label Oct 7, 2024
@LuSrackhall
Copy link
Author

LuSrackhall commented Oct 7, 2024

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)
}

@LuSrackhall
Copy link
Author

LuSrackhall commented Oct 7, 2024

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 Set("key", nil) actions.

tips: I suddenly realized that when using Viper, it seems difficult to easily delete an existing configuration item. (Of course, I will discuss this in a newly created issue.)

Before trying, chatGPT told me that although Viper does not directly provide an API to delete a configuration item, it can be indirectly deleted by using Set("key", nil). However, I now find that this method does not seem to work -- it still doesn’t work even after canceling WatchConfig().

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中一次可能存在的错误删除行为")
		}
	}
}

@LuSrackhall
Copy link
Author

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 Set("key", nil) actions.

tips: I suddenly realized that when using Viper, it seems difficult to easily delete an existing configuration item. (Of course, I will discuss this in a newly created issue.)

Before trying, chatGPT told me that although Viper does not directly provide an API to delete a configuration item, it can be indirectly deleted by using Set("key", nil). However, I now find that this method does not seem to work -- it still doesn’t work even after canceling WatchConfig().

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中一次可能存在的错误删除行为")
		}
	}
}

#1935

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant