diff --git a/pkg/commands/cmd_reload.go b/pkg/commands/cmd_reload.go index e4c65366c..07ab44016 100644 --- a/pkg/commands/cmd_reload.go +++ b/pkg/commands/cmd_reload.go @@ -14,7 +14,7 @@ func reloadCommand() Definition { if err := rt.ReloadConfig(); err != nil { return req.Reply("Failed to reload configuration: " + err.Error()) } - return req.Reply("Configuration reloaded successfully!") + return req.Reply("Config reload triggered!") }, } } diff --git a/pkg/gateway/gateway.go b/pkg/gateway/gateway.go index 9e34dbcce..ee7815fe2 100644 --- a/pkg/gateway/gateway.go +++ b/pkg/gateway/gateway.go @@ -7,6 +7,7 @@ import ( "os/signal" "path/filepath" "sync" + "sync/atomic" "syscall" "time" @@ -55,6 +56,7 @@ type services struct { DeviceService *devices.Service HealthServer *health.Server manualReloadChan chan struct{} + reloading atomic.Bool } type startupBlockedProvider struct { @@ -122,11 +124,16 @@ func Run(debug bool, configPath string, allowEmptyStartup bool) error { manualReloadChan := make(chan struct{}, 1) runningServices.manualReloadChan = manualReloadChan reloadTrigger := func() error { + if !runningServices.reloading.CompareAndSwap(false, true) { + return fmt.Errorf("reload already in progress") + } select { case manualReloadChan <- struct{}{}: return nil 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) @@ -158,7 +165,11 @@ func Run(debug bool, configPath string, allowEmptyStartup bool) error { shutdownGateway(runningServices, agentLoop, provider, true) return nil 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 { 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) if err != nil { logger.Errorf("Error loading config for manual reload: %v", err) + runningServices.reloading.Store(false) continue } if err = newCfg.ValidateModelList(); err != nil { logger.Errorf("Config validation failed: %v", err) + runningServices.reloading.Store(false) continue } - err = handleConfigReload(ctx, agentLoop, newCfg, &provider, runningServices, msgBus, allowEmptyStartup) + err = executeReload(ctx, agentLoop, newCfg, &provider, runningServices, msgBus, allowEmptyStartup) if err != nil { logger.Errorf("Manual reload failed: %v", err) } 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( cfg *config.Config, allowEmptyStartup bool, diff --git a/pkg/health/server.go b/pkg/health/server.go index 226a0ba96..fe20e4b94 100644 --- a/pkg/health/server.go +++ b/pkg/health/server.go @@ -18,7 +18,6 @@ type Server struct { checks map[string]Check startTime time.Time reloadFunc func() error - reloadMu sync.Mutex } 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. func (s *Server) SetReloadFunc(fn func() error) { - s.reloadMu.Lock() - defer s.reloadMu.Unlock() + s.mu.Lock() + defer s.mu.Unlock() s.reloadFunc = fn } @@ -124,9 +123,9 @@ func (s *Server) reloadHandler(w http.ResponseWriter, r *http.Request) { return } - s.reloadMu.Lock() + s.mu.Lock() reloadFunc := s.reloadFunc - s.reloadMu.Unlock() + s.mu.Unlock() if reloadFunc == nil { w.Header().Set("Content-Type", "application/json")