Files
geo/server/internal/shared/config/reload.go
T
root 618399f86d feat(server): hot-reload config store and reloadable infra clients
- Replace single Load() with a watching Store that re-reads config files
  (and config.local.yaml) and fans out a ReloadEvent with a per-field diff
  so consumers can decide whether the change is hot-applicable or requires
  a process restart.
- Wrap llm, retrieval, vector store, and object storage clients in
  Reloadable* shells so the bootstrap can swap their underlying impls when
  config changes without re-instantiating handlers.
- Make jwt.Manager and ops TokenIssuer mutable under a lock so secrets and
  TTLs can be rotated live; thread default plan code through a setter on
  the ops AdminUserService.
- Wire ConfigStore through bootstrap and every cmd/main.go, scheduler /
  worker / tenant-api / ops-api start the watcher; services and handlers
  take a config.Provider so they always read current values for things
  like generation.stream_enabled, scheduler dispatch, retrieval, etc.
- Switch shared/config decoding off viper to a Kratos-derived runtime
  package so env placeholders (\${VAR:default}) resolve consistently and
  the same source machinery powers both the loader and the watcher.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 16:01:23 +08:00

113 lines
3.1 KiB
Go

package config
import "reflect"
type FieldChange struct {
Path string
Hot bool
}
func Diff(previous, current *Config) []FieldChange {
if previous == nil || current == nil {
return nil
}
changes := make([]FieldChange, 0)
addChange := func(path string, hot bool) {
changes = append(changes, FieldChange{Path: path, Hot: hot})
}
if previous.Server != current.Server {
addChange("server", false)
}
if previous.Database != current.Database {
addChange("database", false)
}
if previous.MonitoringDatabase != current.MonitoringDatabase {
addChange("monitoring_database", false)
}
if previous.RabbitMQ != current.RabbitMQ {
addChange("rabbitmq", false)
}
if previous.Scheduler.HTTPHost != current.Scheduler.HTTPHost ||
previous.Scheduler.HTTPPort != current.Scheduler.HTTPPort {
addChange("scheduler.http", false)
}
if previous.Redis != current.Redis {
addChange("redis", false)
}
if previous.Cache != current.Cache {
addChange("cache", false)
}
if previous.Log != current.Log {
addChange("log", false)
}
if previous.Scheduler.InternalMetricsToken != current.Scheduler.InternalMetricsToken {
addChange("scheduler.internal_metrics_token", true)
}
if previous.Scheduler.DispatchInterval != current.Scheduler.DispatchInterval ||
previous.Scheduler.DispatchTimeout != current.Scheduler.DispatchTimeout ||
previous.Scheduler.DispatchBatchSize != current.Scheduler.DispatchBatchSize ||
previous.Scheduler.DispatchConcurrency != current.Scheduler.DispatchConcurrency ||
previous.Scheduler.GenerationQueueBackpressureLimit != current.Scheduler.GenerationQueueBackpressureLimit {
addChange("scheduler.dispatch", true)
}
if previous.MonitoringWorkers != current.MonitoringWorkers {
addChange("monitoring_workers", true)
}
if previous.MonitoringDispatch != current.MonitoringDispatch {
addChange("monitoring_dispatch", true)
}
if !reflect.DeepEqual(previous.Membership, current.Membership) {
addChange("membership", true)
}
if previous.BrandLibrary != current.BrandLibrary {
addChange("brand_library", true)
}
if previous.Qdrant != current.Qdrant {
addChange("qdrant", true)
}
if previous.ObjectStorage != current.ObjectStorage {
addChange("object_storage", true)
}
if previous.JWT != current.JWT {
addChange("jwt", true)
}
if previous.LLM != current.LLM {
addChange("llm", true)
}
if previous.Retrieval != current.Retrieval {
addChange("retrieval", true)
}
if previous.Generation.QueueSize != current.Generation.QueueSize ||
previous.Generation.WorkerConcurrency != current.Generation.WorkerConcurrency {
addChange("generation.worker", false)
}
if previous.Generation.StreamEnabled != current.Generation.StreamEnabled ||
previous.Generation.ArticleTimeout != current.Generation.ArticleTimeout {
addChange("generation", true)
}
return changes
}
func RestartRequired(changes []FieldChange) bool {
for _, change := range changes {
if !change.Hot {
return true
}
}
return false
}
func ChangePaths(changes []FieldChange, hot bool) []string {
paths := make([]string, 0, len(changes))
for _, change := range changes {
if change.Hot == hot {
paths = append(paths, change.Path)
}
}
return paths
}