V
V
vgrabkowot2021-04-30 20:41:42
go
vgrabkowot, 2021-04-30 20:41:42

Will there be race and data corruption in memory?

I store the configuration in one json file, plus the configuration must be thread-safe without smearing mutexes throughout the code (in my experience, finding a missing mutex is still suffering) and unnecessary copying.
As a result, I received a similar API:

cfg := config.Read()
defer cfg.Close()
ctx, cancel := context.WithTimeout(context.Background(), cfg.HTTP.Timeout.StopServer*time.Second)

Did I miss something? This is the first time I'm solving this problem. Is there a better solution?
package config

import (
  "encoding/json"
  "os"
  "sync"
)

var (
  configPool sync.Pool
  cfgSync    = &sync.RWMutex{}
  cfgValue   *Cfg
)

func Read() *Cfg {
  var c *Cfg
  if v := configPool.Get(); v != nil { //если в пуле есть "свободные" указатели то возвращаем их
    return v.(*Cfg)
  } else { //пул пуст, создаем новый указатель
    c = newCfg()
    valid(c)
    configPool.Put(c)
    return Read()
  }
}
func (cfg *Cfg) Close() {
  configPool.Put(cfg)
}
func newCfg() *Cfg {
  cfgSync.RLock()
  is := cfgValue == nil
  cfgSync.RUnlock()
  if is { //файл незагружен в память, загружаем
    f, err := os.Open("./config/config.json")
    if err != nil {
      panic("Error open config file: `./config/config.json `" + err.Error())
    }
    defer f.Close()
    dec := json.NewDecoder(f)
    cfg := &Cfg{}
    if err := dec.Decode(cfg); err != nil {
      panic("Error parse config file: " + err.Error())
    }

    cfgSync.Lock()
    cfgValue = cfg
    cfgSync.Unlock()
  }
  //создаем новый указатель
  ret := new(Cfg)

  //копируем из старого в новый указатель
  cfgSync.RLock()
  *ret = *cfgValue
  cfgSync.RUnlock()
  //возвращаем копию
  return ret
}

Answer the question

In order to leave comments, you need to log in

1 answer(s)
F
falconandy, 2021-04-30
@vgrabkowot

I think you're overcomplicating unnecessarily. If the values ​​from the config are only readable, then thread safety is not needed. If you also expect a change, then pointers to "corrupted" objects are returned to the pool, which is also wrong.
If possible, it is better to read the config at the beginning of the execution, and also panic at the main level.
As a result, the code is simpler, and there is nothing to slow down:

package config

import (
  "encoding/json"
  "fmt"
  "os"
)

type Cfg struct {
  // Fields
}

var (
  cfg *Cfg
)

func Config() *Cfg {
  return cfg
}

func LoadConfig() error {
  f, err := os.Open("./config/config.json")
  if err != nil {
    return fmt.Errorf("can't open config file: %w", err)
  }
  defer f.Close()

  dec := json.NewDecoder(f)
  var c *Cfg
  if err := dec.Decode(c); err != nil {
    return fmt.Errorf("can't decode config file: %w", err)
  }

  if err := validate(c); err != nil {
    return fmt.Errorf("can't validate config file: %w", err)
  }
  
  cfg = c

  return nil
}

func validate(cfg *Cfg) error {
  // Logic
  return nil
}

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question