initial commit
This commit is contained in:
1 parent
5b935301e5
commit
85453852d5
105 files changed
+24846
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,126 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Store is the process-wide, hot-reloadable configuration holder. Readers take
|
||||
// a snapshot pointer (cheap, no lock) and writers swap a fully-built *Config
|
||||
// in atomically, so a request never observes a half-applied config.
|
||||
type Store struct {
|
||||
cur atomic.Pointer[Config]
|
||||
mu sync.Mutex // serialises writers so LastError/Version stay coherent
|
||||
// path is the file the config was loaded from, used by the API to
|
||||
// persist mutations.
|
||||
path string
|
||||
// lastError records why the most recent reload attempt failed, so the
|
||||
// health endpoint can report a stale-but-running state.
|
||||
lastErr atomic.Pointer[string]
|
||||
// generation increments on every successful swap.
|
||||
generation atomic.Uint64
|
||||
// hooks run after a successful swap (server re-binds listeners etc).
|
||||
hooksMu sync.RWMutex
|
||||
hooks []func(*Config)
|
||||
}
|
||||
|
||||
// NewStore wraps cfg.
|
||||
func NewStore(cfg *Config, path string) *Store {
|
||||
s := &Store{path: path}
|
||||
s.cur.Store(cfg)
|
||||
return s
|
||||
}
|
||||
|
||||
// Get returns the current configuration snapshot.
|
||||
func (s *Store) Get() *Config { return s.cur.Load() }
|
||||
|
||||
// Path is the file this config was loaded from ("" when running without one).
|
||||
func (s *Store) Path() string { return s.path }
|
||||
|
||||
// Generation counts successful swaps; the cluster uses it to detect
|
||||
// divergence between instances.
|
||||
func (s *Store) Generation() uint64 { return s.generation.Load() }
|
||||
|
||||
// LastError returns the error from the most recent failed reload, or nil.
|
||||
func (s *Store) LastError() string {
|
||||
if p := s.lastErr.Load(); p != nil {
|
||||
return *p
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Swap installs cfg as the current configuration. Callers are responsible for
|
||||
// validating first; Swap itself only guards structural invariants that would
|
||||
// otherwise panic subsystems.
|
||||
func (s *Store) Swap(cfg *Config) error {
|
||||
if cfg == nil {
|
||||
return errNilConfig
|
||||
}
|
||||
if cfg.Cache.Shards <= 0 {
|
||||
cfg.Cache.Shards = 32
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.cur.Store(cfg)
|
||||
s.generation.Add(1)
|
||||
s.lastErr.Store(nil)
|
||||
s.mu.Unlock()
|
||||
s.runHooks(cfg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reload re-reads the backing file and swaps it in on success. On failure the
|
||||
// previous configuration stays active and the error is recorded.
|
||||
func (s *Store) Reload() error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.path == "" {
|
||||
return errNoPath
|
||||
}
|
||||
cfg, err := Load(s.path)
|
||||
if err != nil {
|
||||
msg := err.Error()
|
||||
s.lastErr.Store(&msg)
|
||||
return err
|
||||
}
|
||||
// Preserve the working dir of the running instance so relative paths keep
|
||||
// resolving the same way even if the file moved.
|
||||
cfg.Server.WorkingDir = s.cur.Load().Server.WorkingDir
|
||||
s.cur.Store(cfg)
|
||||
s.generation.Add(1)
|
||||
s.lastErr.Store(nil)
|
||||
s.runHooks(cfg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnSwap registers a callback invoked with the new config after each swap.
|
||||
// Hooks run outside the writer lock; a panicking hook is contained so one bad
|
||||
// subsystem cannot take down a reload.
|
||||
func (s *Store) OnSwap(fn func(*Config)) {
|
||||
if fn == nil {
|
||||
return
|
||||
}
|
||||
s.hooksMu.Lock()
|
||||
s.hooks = append(s.hooks, fn)
|
||||
s.hooksMu.Unlock()
|
||||
}
|
||||
|
||||
func (s *Store) runHooks(cfg *Config) {
|
||||
s.hooksMu.RLock()
|
||||
hooks := s.hooks
|
||||
s.hooksMu.RUnlock()
|
||||
for _, h := range hooks {
|
||||
func() {
|
||||
defer func() { _ = recover() }()
|
||||
h(cfg)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
type storeError string
|
||||
|
||||
func (e storeError) Error() string { return string(e) }
|
||||
|
||||
const (
|
||||
errNilConfig = storeError("config: refusing to swap a nil configuration")
|
||||
errNoPath = storeError("config: no config file path known, cannot reload")
|
||||
)
|
||||
Reference in new issue
Block a user