diff --git a/internal/routewatch/app.go b/internal/routewatch/app.go index a4d9b69..9354614 100644 --- a/internal/routewatch/app.go +++ b/internal/routewatch/app.go @@ -8,6 +8,7 @@ import ( "log/slog" "os" "strings" + "sync" "time" "git.eeqj.de/sneak/routewatch/internal/database" @@ -58,6 +59,8 @@ type RouteWatch struct { snapshotter *snapshotter.Snapshotter logger *slog.Logger maxRuntime time.Duration + shutdown bool + mu sync.Mutex } // New creates a new RouteWatch instance @@ -130,6 +133,20 @@ func (rw *RouteWatch) Run(ctx context.Context) error { // Wait for context cancellation <-ctx.Done() + return nil +} + +// Shutdown performs graceful shutdown of all services +func (rw *RouteWatch) Shutdown() { + rw.mu.Lock() + if rw.shutdown { + rw.mu.Unlock() + + return + } + rw.shutdown = true + rw.mu.Unlock() + // Stop services rw.streamer.Stop() @@ -153,15 +170,15 @@ func (rw *RouteWatch) Run(ctx context.Context) error { // Take final snapshot before shutdown if snapshotter is available if rw.snapshotter != nil { - rw.logger.Info("Shutting down snapshotter") + rw.logger.Info("Taking final snapshot before shutdown") if err := rw.snapshotter.Shutdown(); err != nil { rw.logger.Error("Failed to shutdown snapshotter", "error", err) + } else { + rw.logger.Info("Final snapshot completed") } } else { rw.logger.Info("No snapshotter available") } - - return nil } // logRoutingTableStats periodically logs routing table statistics diff --git a/internal/routewatch/cli.go b/internal/routewatch/cli.go index 3c5b9b8..67ca45f 100644 --- a/internal/routewatch/cli.go +++ b/internal/routewatch/cli.go @@ -40,6 +40,7 @@ func CLIEntry() { }, OnStop: func(_ context.Context) error { logger.Info("Shutting down RouteWatch") + rw.Shutdown() return nil },