diff --git a/cmd/run.go b/cmd/run.go index 56a45a3..53371f0 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -314,21 +314,23 @@ var runCmd = &cobra.Command{ } }() } + nodeApp.Fallback(func(ctx context.Context, cs *corestate.CoreState, x *app.AppX) { + if err := srv.Shutdown(ctxMain); err != nil { + x.Log.Printf("%s: Failed to stop the server gracefully: %s", logs.PrintError(), err.Error()) + } else { + x.Log.Printf("Server stopped gracefully") + } + + x.Log.Println("Cleaning up...") + + if err := run_manager.Clean(); err != nil { + x.Log.Printf("%s: Cleanup error: %s", logs.PrintError(), err.Error()) + } + x.Log.Println("bye!") + }) <-ctxMain.Done() - if err := srv.Shutdown(ctxMain); err != nil { - x.Log.Printf("%s: Failed to stop the server gracefully: %s", logs.PrintError(), err.Error()) - } else { - x.Log.Printf("Server stopped gracefully") - } - - x.Log.Println("Cleaning up...") - - if err := run_manager.Clean(); err != nil { - x.Log.Printf("%s: Cleanup error: %s", logs.PrintError(), err.Error()) - } - x.Log.Println("bye!") - + nodeApp.CallFallback(ctx) return nil }) }, diff --git a/internal/engine/app/app.go b/internal/engine/app/app.go index 91784f7..9c43a0f 100644 --- a/internal/engine/app/app.go +++ b/internal/engine/app/app.go @@ -6,6 +6,7 @@ import ( "log/slog" "os" "os/signal" + "sync" "syscall" "github.com/akyaiy/GoSally-mvp/internal/core/corestate" @@ -16,14 +17,20 @@ import ( type AppContract interface { InitialHooks(fn ...func(cs *corestate.CoreState, x *AppX)) Run(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX) error) + Fallback(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX)) + + CallFallback(ctx context.Context) } type App struct { initHooks []func(cs *corestate.CoreState, x *AppX) runHook func(ctx context.Context, cs *corestate.CoreState, x *AppX) error + fallback func(ctx context.Context, cs *corestate.CoreState, x *AppX) Corestate *corestate.CoreState AppX *AppX + + fallbackOnce sync.Once } type AppX struct { @@ -46,6 +53,10 @@ func (a *App) InitialHooks(fn ...func(cs *corestate.CoreState, x *AppX)) { a.initHooks = append(a.initHooks, fn...) } +func (a *App) Fallback(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX)) { + a.fallback = fn +} + func (a *App) Run(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX) error) { a.runHook = fn @@ -56,9 +67,30 @@ func (a *App) Run(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX) ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) defer stop() - if a.runHook != nil { - if err := a.runHook(ctx, a.Corestate, a.AppX); err != nil { - log.Fatalf("fatal in Run: %v", err) + defer func() { + if r := recover(); r != nil { + a.AppX.Log.Printf("PANIC recovered: %v", r) + if a.fallback != nil { + a.fallback(ctx, a.Corestate, a.AppX) + } + os.Exit(1) } + }() + + var runErr error + if a.runHook != nil { + runErr = a.runHook(ctx, a.Corestate, a.AppX) + } + + if runErr != nil { + a.AppX.Log.Fatalf("fatal in Run: %v", runErr) } } + +func (a *App) CallFallback(ctx context.Context) { + a.fallbackOnce.Do(func() { + if a.fallback != nil { + a.fallback(ctx, a.Corestate, a.AppX) + } + }) +}