Hot-Reload Config with atomic.Value
Swap immutable configuration snapshots atomically so readers never take a lock, while validating each new version first.
Code
package main
import (
"fmt"
"sync"
"sync/atomic"
"time"
)
type Config struct {
Environment string
Timeout time.Duration
Features []string
}
func (c Config) Validate() error {
if c.Environment == "" {
return fmt.Errorf("environment must not be empty")
}
if c.Timeout <= 0 {
return fmt.Errorf("timeout must be positive")
}
return nil
}
func (c Config) clone() Config {
copied := c
copied.Features = append([]string(nil), c.Features...)
return copied
}
type Store struct {
current atomic.Value
}
func NewStore(initial Config) (*Store, error) {
initial = initial.clone()
if err := initial.Validate(); err != nil {
return nil, err
}
store := &Store{}
store.current.Store(initial)
return store, nil
}
func (s *Store) Load() Config {
return s.current.Load().(Config).clone()
}
func (s *Store) Update(next Config) error {
next = next.clone()
if err := next.Validate(); err != nil {
return err
}
s.current.Store(next)
return nil
}
func main() {
store, err := NewStore(Config{
Environment: "staging",
Timeout: 2 * time.Second,
Features: []string{"beta"},
})
if err != nil {
fmt.Println(err)
return
}
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 3; i++ {
snapshot := store.Load()
fmt.Printf(
"reader: env=%s timeout=%s features=%v\n",
snapshot.Environment,
snapshot.Timeout,
snapshot.Features,
)
time.Sleep(10 * time.Millisecond)
}
}()
time.Sleep(5 * time.Millisecond)
if err := store.Update(Config{
Environment: "production",
Timeout: time.Second,
Features: []string{"beta", "search"},
}); err != nil {
fmt.Println("update:", err)
}
invalid := Config{Environment: "", Timeout: time.Second}
if err := store.Update(invalid); err != nil {
fmt.Println("rejected:", err)
}
wg.Wait()
}
Report a problem with this exampleOpens a pre-filled issue on GitHub.