spf13 / viper

Go configuration with fangs
MIT License
27.01k stars 2.01k forks source link

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 LuSrackhall opened 1 week ago

LuSrackhall commented 1 week ago

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

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 commented 1 week ago

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 commented 1 week ago

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 commented 1 week ago

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