package monitoring import ( "context" "sync" "time" "github.com/rcourtman/pulse-go-rewrite/internal/config" "github.com/rcourtman/pulse-go-rewrite/internal/websocket" "github.com/rs/zerolog/log" ) // ReloadableMonitor wraps a Monitor with reload capability type ReloadableMonitor struct { mu sync.RWMutex monitor *Monitor config *config.Config wsHub *websocket.Hub ctx context.Context cancel context.CancelFunc parentCtx context.Context reloadChan chan chan error } // NewReloadableMonitor creates a new reloadable monitor func NewReloadableMonitor(cfg *config.Config, wsHub *websocket.Hub) (*ReloadableMonitor, error) { monitor, err := New(cfg) if err != nil { return nil, err } rm := &ReloadableMonitor{ monitor: monitor, config: cfg, wsHub: wsHub, reloadChan: make(chan chan error, 1), } return rm, nil } // Start starts the monitor with reload capability func (rm *ReloadableMonitor) Start(ctx context.Context) { rm.mu.Lock() rm.parentCtx = ctx rm.ctx, rm.cancel = context.WithCancel(ctx) rm.mu.Unlock() // Start the monitor go rm.monitor.Start(rm.ctx, rm.wsHub) // Watch for reload signals go rm.watchReload(ctx) } // Reload triggers a monitor reload func (rm *ReloadableMonitor) Reload() error { done := make(chan error, 1) rm.reloadChan <- done return <-done } // watchReload watches for reload signals func (rm *ReloadableMonitor) watchReload(ctx context.Context) { for { select { case <-ctx.Done(): return case done := <-rm.reloadChan: log.Info().Msg("Reloading monitor configuration") if err := rm.doReload(); err != nil { log.Error().Err(err).Msg("Failed to reload monitor") done <- err } else { log.Info().Msg("Monitor reloaded successfully") done <- nil } } } } // doReload performs the actual reload func (rm *ReloadableMonitor) doReload() error { rm.mu.Lock() defer rm.mu.Unlock() // Load fresh configuration cfg, err := config.Load() if err != nil { return err } // Note: Polling interval is now hardcoded to 10s, no special handling needed // For other changes, do a full reload log.Info().Msg("Performing full monitor reload") // Cancel current monitor if rm.cancel != nil { rm.cancel() } // Wait a moment for cleanup time.Sleep(1 * time.Second) // Create new monitor newMonitor, err := New(cfg) if err != nil { // Restart old monitor if new one fails rm.ctx, rm.cancel = context.WithCancel(rm.parentCtx) go rm.monitor.Start(rm.ctx, rm.wsHub) return err } // Replace monitor rm.monitor = newMonitor rm.config = cfg // Start new monitor rm.ctx, rm.cancel = context.WithCancel(rm.parentCtx) go rm.monitor.Start(rm.ctx, rm.wsHub) return nil } // Note: Polling interval change detection removed - now hardcoded to 10s // GetMonitor returns the current monitor instance func (rm *ReloadableMonitor) GetMonitor() *Monitor { rm.mu.RLock() defer rm.mu.RUnlock() return rm.monitor } // GetConfig returns the current configuration used by the monitor. func (rm *ReloadableMonitor) GetConfig() *config.Config { rm.mu.RLock() defer rm.mu.RUnlock() if rm.config == nil { return nil } return rm.config } // GetState returns the current state func (rm *ReloadableMonitor) GetState() interface{} { return rm.GetMonitor().GetState() } // Stop stops the monitor func (rm *ReloadableMonitor) Stop() { rm.mu.Lock() defer rm.mu.Unlock() if rm.cancel != nil { rm.cancel() } if rm.monitor != nil { rm.monitor.Stop() } } // Note: UpdatePollingInterval removed - polling interval is now hardcoded to 10s