package app import ( "context" "log" "os" "os/signal" "sync" "syscall" "time" "github.com/labstack/echo/v4" ) // Handler interface for services that need lifecycle management type Handler interface { StartScheduler() StopScheduler() } // App manages application lifecycle and graceful shutdown type App struct { echo *echo.Echo handler Handler shutdownTimeout time.Duration shutdownMutex sync.Mutex shutdownDone chan struct{} } // New creates a new App instance func New(echo *echo.Echo, handler Handler) *App { return &App{ echo: echo, handler: handler, shutdownTimeout: 30 * time.Second, shutdownDone: make(chan struct{}), } } // Start begins all background services and blocks until shutdown signal func (a *App) Start() error { log.Println("Starting application lifecycle management...") // Start background services a.startBackgroundServices() // Setup signal handling for graceful shutdown sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, // Ctrl+C syscall.SIGTERM, // kill syscall.SIGQUIT, // quit ) // Wait for shutdown signal sig := <-sigChan log.Printf("Received signal: %v. Initiating graceful shutdown...", sig) // Perform graceful shutdown if err := a.Shutdown(); err != nil { log.Printf("Error during shutdown: %v", err) return err } log.Println("Application shutdown complete") return nil } // startBackgroundServices starts all background services func (a *App) startBackgroundServices() { log.Println("Starting background services...") // Start scheduler for auto-scanning go func() { a.handler.StartScheduler() log.Println("Scheduler started") }() // Note: Watch mode is started by the handlers package // after a 2-second delay, so we don't duplicate it here } // Shutdown performs graceful shutdown of all services func (a *App) Shutdown() error { a.shutdownMutex.Lock() defer a.shutdownMutex.Unlock() select { case <-a.shutdownDone: log.Println("Shutdown already in progress") return nil default: // Continue with shutdown } log.Println("Beginning graceful shutdown...") // Create context with timeout ctx, cancel := context.WithTimeout(context.Background(), a.shutdownTimeout) defer cancel() // Channel to track shutdown completion done := make(chan struct{}) // Perform shutdown in goroutine go func() { defer close(done) // Stop accepting new connections and shutdown HTTP server log.Println("Stopping HTTP server...") if err := a.echo.Close(); err != nil { log.Printf("Error stopping HTTP server: %v", err) } // Stop scheduler log.Println("Stopping scheduler...") a.handler.StopScheduler() log.Println("All services stopped") }() // Wait for shutdown or timeout select { case <-done: close(a.shutdownDone) log.Println("Graceful shutdown completed successfully") return nil case <-ctx.Done(): close(a.shutdownDone) log.Printf("Shutdown timed out after %v", a.shutdownTimeout) return ctx.Err() } } // SetShutdownTimeout sets the maximum time to wait for graceful shutdown func (a *App) SetShutdownTimeout(timeout time.Duration) { a.shutdownTimeout = timeout } // ShutdownDone returns a channel that closes when shutdown is complete func (a *App) ShutdownDone() <-chan struct{} { return a.shutdownDone }