129 lines
2.9 KiB
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
|
|
}
|
|
}
|
|
}
|