# Live Reconfiguration The config package supports automatic configuration reloading when files change, enabling zero-downtime reconfiguration. ## Basic File Watching ### Enable Auto-Update ```go cfg, _ := config.NewBuilder(). WithDefaults(&Config{}). WithFile("config.toml"). Build() // Enable automatic reloading cfg.AutoUpdate() // Your application continues running // Config reloads automatically when file changes // Stop watching when done defer cfg.StopAutoUpdate() ``` ### Watch for Changes ```go // Get notified of configuration changes changes := cfg.Watch() go func() { for path := range changes { log.Printf("Configuration changed: %s", path) // React to specific changes switch path { case "server.port": // Restart server with new port restartServer() case "log.level": // Update log level updateLogLevel() } } }() ``` ## Watch Options ### Custom Watch Configuration ```go opts := config.WatchOptions{ PollInterval: 500 * time.Millisecond, // Check every 500ms Debounce: 200 * time.Millisecond, // Wait 200ms after changes MaxWatchers: 50, // Limit concurrent watchers ReloadTimeout: 10 * time.Second, // Timeout for reload VerifyPermissions: true, // Security check } cfg.AutoUpdateWithOptions(opts) ``` ### Watch Without Auto-Update ```go // Just watch, don't auto-reload changes := cfg.WatchWithOptions(config.WatchOptions{ PollInterval: time.Second, }) // Manually reload when desired go func() { for range changes { if shouldReload() { cfg.LoadFile("config.toml") } } }() ``` ## Change Detection ### Value Changes The watcher detects and notifies about: - New values added - Existing values modified - Values removed - Type changes ```go changes := cfg.Watch() for path := range changes { newVal, exists := cfg.Get(path) if !exists { log.Printf("Removed: %s", path) continue } sources := cfg.GetSources(path) fileVal, hasFile := sources[config.SourceFile] log.Printf("Changed: %s = %v (from file: %v)", path, newVal, hasFile) } ``` ### Special Notifications ```go changes := cfg.Watch() for notification := range changes { switch notification { case "file_deleted": log.Warn("Config file was deleted") case "permissions_changed": log.Error("Config file permissions changed - potential security issue") case "reload_timeout": log.Error("Config reload timed out") default: if strings.HasPrefix(notification, "reload_error:") { log.Error("Reload error:", notification) } else { // Normal path change handleConfigChange(notification) } } } ``` ## Debouncing Rapid file changes are automatically debounced: ```go // Multiple rapid saves to config.toml // Only triggers one reload after debounce period opts := config.WatchOptions{ PollInterval: 100 * time.Millisecond, Debounce: 500 * time.Millisecond, // Wait 500ms } cfg.AutoUpdateWithOptions(opts) ``` ## Permission Monitoring ```go opts := config.WatchOptions{ VerifyPermissions: true, // Enabled by default } cfg.AutoUpdateWithOptions(opts) // Detects if file becomes world-writable changes := cfg.Watch() for change := range changes { if change == "permissions_changed" { // File permissions changed // Possible security breach alert("Config file permissions modified!") } } ``` ## Pattern: Reconfiguration ```go type Server struct { cfg *config.Config listener net.Listener mu sync.RWMutex } func (s *Server) watchConfig() { changes := s.cfg.Watch() for path := range changes { switch { case strings.HasPrefix(path, "server."): s.scheduleRestart() case path == "log.level": s.updateLogLevel() case strings.HasPrefix(path, "feature."): s.reloadFeatures() } } } func (s *Server) scheduleRestart() { s.mu.Lock() defer s.mu.Unlock() // Graceful restart logic log.Info("Scheduling server restart for config changes") // ... drain connections, restart listener ... } ``` ## Pattern: Feature Flags ```go type FeatureFlags struct { cfg *config.Config mu sync.RWMutex } func (ff *FeatureFlags) Watch() { changes := ff.cfg.Watch() for path := range changes { if strings.HasPrefix(path, "features.") { feature := strings.TrimPrefix(path, "features.") enabled, _ := ff.cfg.Get(path) log.Printf("Feature %s: %v", feature, enabled) ff.notifyFeatureChange(feature, enabled.(bool)) } } } func (ff *FeatureFlags) IsEnabled(feature string) bool { ff.mu.RLock() defer ff.mu.RUnlock() val, exists := ff.cfg.Get("features." + feature) return exists && val.(bool) } ``` ## Pattern: Multi-Stage Reload ```go func watchConfigWithValidation(cfg *config.Config) { changes := cfg.Watch() for range changes { // Stage 1: Snapshot current config backup := cfg.Clone() // Stage 2: Validate new configuration if err := validateNewConfig(cfg); err != nil { log.Error("Invalid configuration:", err) continue } // Stage 3: Apply changes if err := applyConfigChanges(cfg, backup); err != nil { log.Error("Failed to apply changes:", err) // Could restore from backup here continue } log.Info("Configuration successfully reloaded") } } ``` ## Monitoring ### Watch Status ```go // Check if watching is active if cfg.IsWatching() { log.Printf("Auto-update is enabled") log.Printf("Active watchers: %d", cfg.WatcherCount()) } ``` ### Resource Management ```go // Limit watchers to prevent resource exhaustion opts := config.WatchOptions{ MaxWatchers: 10, // Max 10 concurrent watch channels } // Watchers beyond limit receive closed channels cfg.AutoUpdateWithOptions(opts) ``` ## Best Practices 1. **Always Stop Watching**: Use `defer cfg.StopAutoUpdate()` to clean up 2. **Handle All Notifications**: Check for special error notifications 3. **Validate After Reload**: Ensure new config is valid before applying 4. **Use Debouncing**: Prevent reload storms from rapid edits 5. **Monitor Permissions**: Enable permission verification for security 6. **Graceful Updates**: Plan how your app handles config changes 7. **Log Changes**: Audit configuration modifications ## Limitations - File watching uses polling (not inotify/kqueue) - No support for watching multiple files - Changes only detected for registered paths - Reloads entire file (no partial updates) ## Common Issues ### Changes Not Detected ```go // Ensure path is registered before watching cfg.Register("new.value", "default") // Now changes to new.value will be detected ``` ### Rapid Reloads ```go // Increase debounce to prevent rapid reloads opts := config.WatchOptions{ Debounce: 2 * time.Second, // Wait 2s after changes stop } ``` ### Memory Leaks ```go // Always stop watching to prevent goroutine leaks watcher := cfg.Watch() // Use context for cancellation ctx, cancel := context.WithCancel(context.Background()) defer cancel() go func() { for { select { case change := <-watcher: handleChange(change) case <-ctx.Done(): return } } }() ``` ## See Also - [File Configuration](file.md) - File format and loading - [Access Patterns](access.md) - Reacting to changed values - [Builder Pattern](builder.md) - Setting up watching with builder