Hot-Reload Config with atomic.Value

Swap immutable configuration snapshots atomically so readers never take a lock, while validating each new version first.

GoConfig Management

Code

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