prevent duplicate reload request in same time

This commit is contained in:
Cytown 2026-03-18 16:30:52 +08:00
parent cee79ea4dd
commit 9b7bb78676
3 changed files with 34 additions and 9 deletions

View file

@ -14,7 +14,7 @@ func reloadCommand() Definition {
if err := rt.ReloadConfig(); err != nil { if err := rt.ReloadConfig(); err != nil {
return req.Reply("Failed to reload configuration: " + err.Error()) return req.Reply("Failed to reload configuration: " + err.Error())
} }
return req.Reply("Configuration reloaded successfully!") return req.Reply("Config reload triggered!")
}, },
} }
} }

View file

@ -7,6 +7,7 @@ import (
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"sync" "sync"
"sync/atomic"
"syscall" "syscall"
"time" "time"
@ -55,6 +56,7 @@ type services struct {
DeviceService *devices.Service DeviceService *devices.Service
HealthServer *health.Server HealthServer *health.Server
manualReloadChan chan struct{} manualReloadChan chan struct{}
reloading atomic.Bool
} }
type startupBlockedProvider struct { type startupBlockedProvider struct {
@ -122,11 +124,16 @@ func Run(debug bool, configPath string, allowEmptyStartup bool) error {
manualReloadChan := make(chan struct{}, 1) manualReloadChan := make(chan struct{}, 1)
runningServices.manualReloadChan = manualReloadChan runningServices.manualReloadChan = manualReloadChan
reloadTrigger := func() error { reloadTrigger := func() error {
if !runningServices.reloading.CompareAndSwap(false, true) {
return fmt.Errorf("reload already in progress")
}
select { select {
case manualReloadChan <- struct{}{}: case manualReloadChan <- struct{}{}:
return nil return nil
default: default:
return fmt.Errorf("reload already in progress") // Should not happen, but reset flag if channel is full
runningServices.reloading.Store(false)
return fmt.Errorf("reload already queued")
} }
} }
runningServices.HealthServer.SetReloadFunc(reloadTrigger) runningServices.HealthServer.SetReloadFunc(reloadTrigger)
@ -158,7 +165,11 @@ func Run(debug bool, configPath string, allowEmptyStartup bool) error {
shutdownGateway(runningServices, agentLoop, provider, true) shutdownGateway(runningServices, agentLoop, provider, true)
return nil return nil
case newCfg := <-configReloadChan: case newCfg := <-configReloadChan:
err := handleConfigReload(ctx, agentLoop, newCfg, &provider, runningServices, msgBus, allowEmptyStartup) if !runningServices.reloading.CompareAndSwap(false, true) {
logger.Warn("Config reload skipped: another reload is in progress")
continue
}
err := executeReload(ctx, agentLoop, newCfg, &provider, runningServices, msgBus, allowEmptyStartup)
if err != nil { if err != nil {
logger.Errorf("Config reload failed: %v", err) logger.Errorf("Config reload failed: %v", err)
} }
@ -167,13 +178,15 @@ func Run(debug bool, configPath string, allowEmptyStartup bool) error {
newCfg, err := config.LoadConfig(configPath) newCfg, err := config.LoadConfig(configPath)
if err != nil { if err != nil {
logger.Errorf("Error loading config for manual reload: %v", err) logger.Errorf("Error loading config for manual reload: %v", err)
runningServices.reloading.Store(false)
continue continue
} }
if err = newCfg.ValidateModelList(); err != nil { if err = newCfg.ValidateModelList(); err != nil {
logger.Errorf("Config validation failed: %v", err) logger.Errorf("Config validation failed: %v", err)
runningServices.reloading.Store(false)
continue continue
} }
err = handleConfigReload(ctx, agentLoop, newCfg, &provider, runningServices, msgBus, allowEmptyStartup) err = executeReload(ctx, agentLoop, newCfg, &provider, runningServices, msgBus, allowEmptyStartup)
if err != nil { if err != nil {
logger.Errorf("Manual reload failed: %v", err) logger.Errorf("Manual reload failed: %v", err)
} else { } else {
@ -183,6 +196,19 @@ func Run(debug bool, configPath string, allowEmptyStartup bool) error {
} }
} }
func executeReload(
ctx context.Context,
agentLoop *agent.AgentLoop,
newCfg *config.Config,
provider *providers.LLMProvider,
runningServices *services,
msgBus *bus.MessageBus,
allowEmptyStartup bool,
) error {
defer runningServices.reloading.Store(false)
return handleConfigReload(ctx, agentLoop, newCfg, provider, runningServices, msgBus, allowEmptyStartup)
}
func createStartupProvider( func createStartupProvider(
cfg *config.Config, cfg *config.Config,
allowEmptyStartup bool, allowEmptyStartup bool,

View file

@ -18,7 +18,6 @@ type Server struct {
checks map[string]Check checks map[string]Check
startTime time.Time startTime time.Time
reloadFunc func() error reloadFunc func() error
reloadMu sync.Mutex
} }
type Check struct { type Check struct {
@ -111,8 +110,8 @@ func (s *Server) RegisterCheck(name string, checkFn func() (bool, string)) {
// SetReloadFunc sets the callback function for config reload. // SetReloadFunc sets the callback function for config reload.
func (s *Server) SetReloadFunc(fn func() error) { func (s *Server) SetReloadFunc(fn func() error) {
s.reloadMu.Lock() s.mu.Lock()
defer s.reloadMu.Unlock() defer s.mu.Unlock()
s.reloadFunc = fn s.reloadFunc = fn
} }
@ -124,9 +123,9 @@ func (s *Server) reloadHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
s.reloadMu.Lock() s.mu.Lock()
reloadFunc := s.reloadFunc reloadFunc := s.reloadFunc
s.reloadMu.Unlock() s.mu.Unlock()
if reloadFunc == nil { if reloadFunc == nil {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")