kra-new/internal/conf/runtime.go

129 lines
2.9 KiB
Go

package conf
import (
"sync"
"sync/atomic"
"google.golang.org/protobuf/proto"
)
// Runtime owns the active immutable configuration snapshot. Callers always
// receive clones so a config file reload cannot race with request handling.
type Runtime struct {
snapshot atomic.Pointer[runtimeSnapshot]
mu sync.RWMutex
nextID uint64
listeners map[uint64]func(*Data, *AdminBackend)
}
type runtimeSnapshot struct {
data *Data
admin *AdminBackend
}
func NewRuntime(data *Data, admin *AdminBackend) *Runtime {
runtime := &Runtime{listeners: make(map[uint64]func(*Data, *AdminBackend))}
runtime.Replace(data, admin)
return runtime
}
func cloneData(value *Data) *Data {
if value == nil {
return nil
}
return proto.Clone(value).(*Data)
}
func cloneAdmin(value *AdminBackend) *AdminBackend {
if value == nil {
return nil
}
return proto.Clone(value).(*AdminBackend)
}
func (r *Runtime) Replace(data *Data, admin *AdminBackend) {
data = cloneData(data)
admin = cloneAdmin(admin)
r.snapshot.Store(&runtimeSnapshot{data: data, admin: admin})
r.mu.RLock()
listeners := make([]func(*Data, *AdminBackend), 0, len(r.listeners))
for _, listener := range r.listeners {
listeners = append(listeners, listener)
}
r.mu.RUnlock()
for _, listener := range listeners {
listener(cloneData(data), cloneAdmin(admin))
}
}
// Subscribe registers a callback invoked after every successful runtime
// configuration replacement. It is used by long-lived clients such as the
// logger and other long-lived clients that must follow runtime config changes.
func (r *Runtime) Subscribe(listener func(*Data, *AdminBackend)) func() {
if listener == nil {
return func() {}
}
r.mu.Lock()
r.nextID++
id := r.nextID
r.listeners[id] = listener
r.mu.Unlock()
return func() {
r.mu.Lock()
delete(r.listeners, id)
r.mu.Unlock()
}
}
func (r *Runtime) Data() *Data {
snapshot := r.snapshot.Load()
if snapshot == nil {
return nil
}
return cloneData(snapshot.data)
}
func (r *Runtime) Admin() *AdminBackend {
snapshot := r.snapshot.Load()
if snapshot == nil {
return nil
}
return cloneAdmin(snapshot.admin)
}
func (r *Runtime) Values() (*Data, *AdminBackend) {
snapshot := r.snapshot.Load()
if snapshot == nil {
return nil, nil
}
return cloneData(snapshot.data), cloneAdmin(snapshot.admin)
}
func (r *Runtime) ConfigPath() string {
snapshot := r.snapshot.Load()
if snapshot == nil || snapshot.admin == nil {
return ""
}
return snapshot.admin.ConfigPath
}
func (r *Runtime) UpdateDatabase(database *Data_Database) {
for {
current := r.snapshot.Load()
var data *Data
var admin *AdminBackend
if current != nil {
data = cloneData(current.data)
admin = cloneAdmin(current.admin)
}
if data == nil {
data = &Data{}
}
data.Database = proto.Clone(database).(*Data_Database)
next := &runtimeSnapshot{data: data, admin: admin}
if r.snapshot.CompareAndSwap(current, next) {
return
}
}
}