From 11b7913287e15a5c6cf82ec33af5824f516c92e1 Mon Sep 17 00:00:00 2001 From: Benson Wong Date: Sat, 2 May 2026 16:28:53 -0700 Subject: [PATCH] llama-swap.go: remove debounce, replace fmt.Printlns (#731) small fixes to clean up the main(): - remove the debounced config reload - replace fmt.Println with a proxy.LogMonitor for consistency --- llama-swap.go | 146 +++++++++++++++++++++++++------------------------- 1 file changed, 74 insertions(+), 72 deletions(-) diff --git a/llama-swap.go b/llama-swap.go index 431c3f4b..771c52b0 100644 --- a/llama-swap.go +++ b/llama-swap.go @@ -4,12 +4,12 @@ import ( "context" "flag" "fmt" - "log" "net/http" "os" "os/signal" "path/filepath" - "runtime" + "strings" + "sync" "syscall" "time" @@ -34,24 +34,40 @@ func main() { keyFile := flag.String("tls-key-file", "", "TLS key file") showVersion := flag.Bool("version", false, "show version of build") watchConfig := flag.Bool("watch-config", false, "Automatically reload config file on change") + mainLogger := proxy.NewLogMonitor() flag.Parse() // Parse the command-line flags if *showVersion { - fmt.Printf("version: %s (%s), built at %s\n", version, commit, date) + fmt.Printf("version: %s (%s), built at %s", version, commit, date) os.Exit(0) } conf, err := config.LoadConfig(*configPath) if err != nil { - fmt.Printf("Error loading config: %v\n", err) + mainLogger.Errorf("Error loading config: %", err) os.Exit(1) } if len(conf.Profiles) > 0 { - fmt.Println("WARNING: Profile functionality has been removed in favor of Groups. See the README for more information.") + mainLogger.Warn("Profile functionality has been removed in favor of Groups. See the README for more information.") } + switch strings.ToLower(strings.TrimSpace(conf.LogLevel)) { + case "debug": + mainLogger.SetLogLevel(proxy.LevelDebug) + case "info": + mainLogger.SetLogLevel(proxy.LevelInfo) + case "warn": + mainLogger.SetLogLevel(proxy.LevelWarn) + case "error": + mainLogger.SetLogLevel(proxy.LevelError) + default: + mainLogger.SetLogLevel(proxy.LevelInfo) + } + + mainLogger.Debugf("PID: %d", os.Getpid()) + if mode := os.Getenv("GIN_MODE"); mode != "" { gin.SetMode(mode) } else { @@ -78,15 +94,7 @@ func main() { // Setup channels for server management exitChan := make(chan struct{}) sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) - - // Reload signals (SIGHUP on POSIX, none on Windows — Windows does not - // deliver SIGHUP). Always wired up so `kill -HUP` works regardless of - // --watch-config. - reloadChan := make(chan os.Signal, 1) - if runtime.GOOS != "windows" { - signal.Notify(reloadChan, syscall.SIGHUP) - } + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) // Context that bounds the lifetime of background watcher goroutines. watcherCtx, watcherCancel := context.WithCancel(context.Background()) @@ -97,20 +105,36 @@ func main() { } // Support for watching config and reloading when it changes + reloading := false + var reloadMutex sync.Mutex reloadProxyManager := func() { + reloadMutex.Lock() + if reloading { + reloadMutex.Unlock() + return + } + reloading = true + reloadMutex.Unlock() + defer func() { + reloadMutex.Lock() + reloading = false + reloadMutex.Unlock() + }() + + mainLogger.Info("Reloading Configuration") if currentPM, ok := srv.Handler.(*proxy.ProxyManager); ok { conf, err = config.LoadConfig(*configPath) if err != nil { - fmt.Printf("Warning, unable to reload configuration: %v\n", err) + mainLogger.Warnf("Unable to reload configuration: %v", err) return } - fmt.Println("Configuration Changed") + mainLogger.Debug("Configuration Changed") currentPM.Shutdown() newPM := proxy.New(conf) newPM.SetVersion(date, commit, version) srv.Handler = newPM - fmt.Println("Configuration Reloaded") + mainLogger.Debug("Configuration Reloaded") // wait a few seconds and tell any UI to reload time.AfterFunc(3*time.Second, func() { @@ -121,7 +145,7 @@ func main() { } else { conf, err = config.LoadConfig(*configPath) if err != nil { - fmt.Printf("Error, unable to load configuration: %v\n", err) + mainLogger.Errorf("Unable to load configuration: %v", err) os.Exit(1) } newPM := proxy.New(conf) @@ -132,94 +156,72 @@ func main() { // load the initial proxy manager reloadProxyManager() - debouncedReload := debounce(time.Second, reloadProxyManager) - - // Listen for ConfigFileChangedEvent unconditionally so SIGHUP and the - // poll-based watcher both feed the same debounced reload pipeline. The - // UI also listens for the matching ReloadingStateEnd emitted from - // reloadProxyManager. - defer event.On(func(e proxy.ConfigFileChangedEvent) { - if e.ReloadingState == proxy.ReloadingStateStart { - debouncedReload() - } - })() - - // SIGHUP (or platform-equivalent) → reload. Back-to-back signals collapse - // to one reload via the debounce window, which is the desired behavior. - go func() { - for range reloadChan { - fmt.Println("Received reload signal, reloading configuration") - event.Emit(proxy.ConfigFileChangedEvent{ - ReloadingState: proxy.ReloadingStateStart, - }) - } - }() if *watchConfig { go func() { absConfigPath, err := filepath.Abs(*configPath) if err != nil { - fmt.Printf("Error getting absolute path for watching config file: %v\n", err) + mainLogger.Errorf("watch-config unable to determine absolute path for watching config file: %v", err) return } - fmt.Println("Watching configuration for changes (poll-based, 2s interval)") + mainLogger.Info("Watching configuration for changes (poll-based, 2s interval)") (&configwatcher.Watcher{ Path: absConfigPath, Interval: configwatcher.DefaultInterval, OnChange: func() { - event.Emit(proxy.ConfigFileChangedEvent{ - ReloadingState: proxy.ReloadingStateStart, - }) + reloadProxyManager() }, }).Run(watcherCtx) }() } - // shutdown on signal + // Signal handling go func() { - sig := <-sigChan - fmt.Printf("Received signal %v, shutting down...\n", sig) - watcherCancel() - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - defer cancel() + for { + sig := <-sigChan + switch sig { + case syscall.SIGHUP: + mainLogger.Debug("Received SIGHUP") + reloadProxyManager() + case syscall.SIGINT, syscall.SIGTERM: + mainLogger.Debugf("Received signal %v, shutting down...", sig) + watcherCancel() + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() - if pm, ok := srv.Handler.(*proxy.ProxyManager); ok { - pm.Shutdown() - } else { - fmt.Println("srv.Handler is not of type *proxy.ProxyManager") - } + if pm, ok := srv.Handler.(*proxy.ProxyManager); ok { + pm.Shutdown() + } else { + mainLogger.Error("srv.Handler is not of type *proxy.ProxyManager") + } - if err := srv.Shutdown(ctx); err != nil { - fmt.Printf("Server shutdown error: %v\n", err) + if err := srv.Shutdown(ctx); err != nil { + mainLogger.Errorf("Server shutdown: %v", err) + } + close(exitChan) + return + default: + // do nothing on other signals + } } - close(exitChan) }() // Start server go func() { var err error if useTLS { - fmt.Printf("llama-swap listening with TLS on https://%s\n", *listenStr) + mainLogger.Infof("llama-swap listening with TLS on https://%s", *listenStr) err = srv.ListenAndServeTLS(*certFile, *keyFile) } else { - fmt.Printf("llama-swap listening on http://%s\n", *listenStr) + mainLogger.Infof("llama-swap listening on http://%s", *listenStr) err = srv.ListenAndServe() } if err != nil && err != http.ErrServerClosed { - log.Fatalf("Fatal server error: %v\n", err) + mainLogger.Errorf("Fatal server error: %v", err) + os.Exit(1) } }() // Wait for exit signal <-exitChan } - -func debounce(interval time.Duration, f func()) func() { - var timer *time.Timer - return func() { - if timer != nil { - timer.Stop() - } - timer = time.AfterFunc(interval, f) - } -}