diff options
Diffstat (limited to 'cmd/thoi/main.go')
| -rw-r--r-- | cmd/thoi/main.go | 29 |
1 files changed, 28 insertions, 1 deletions
diff --git a/cmd/thoi/main.go b/cmd/thoi/main.go index cff6eb6..cf31177 100644 --- a/cmd/thoi/main.go +++ b/cmd/thoi/main.go @@ -7,6 +7,7 @@ import ( "os" "os/signal" "syscall" + "time" "thehouseoficarus/internal/admin" "thehouseoficarus/internal/behavior" @@ -76,6 +77,8 @@ func main() { adminEvictRoom := g.EvictCasinoRoom adminReloadCourses := g.CourseStore.ReloadAll + // Both admin transports share one UndoStack over the shared history file. + var sharedUndo *admin.UndoStack if cfg.AdminHTTP.Enabled { adminSrv, err := admin.NewServer(cfg, false, g.AccountStore, g.World, g.ItemStore, g.ObjectStore, g.MobStore, dataDir, adminReloadCourses, adminRewriteRoomIDs, adminEvictRoom, adminRebuildCraftIndex, *configPath, g.RunAdminOnOwner) if err != nil { @@ -84,6 +87,7 @@ func main() { // The raw file editor must refuse a file whose character/account is // online; wired before ListenAndServe so the first request sees it. adminSrv.SetPlayerFileOnlineCheck(g.AdminPlayerFileOnline) + sharedUndo = adminSrv.UndoStack() go func() { if err := adminSrv.ListenAndServe(); err != nil { log.Printf("admin HTTP server error: %v", err) @@ -100,6 +104,9 @@ func main() { // Same online-file refusal as the HTTP editor: both servers expose // the same raw file API. adminSrv.SetPlayerFileOnlineCheck(g.AdminPlayerFileOnline) + if sharedUndo != nil { + adminSrv.SetUndoStack(sharedUndo) + } go func() { if err := adminSrv.ListenAndServe(); err != nil { log.Printf("admin HTTPS server error: %v", err) @@ -133,11 +140,31 @@ func main() { g.SetTransportStop(server.Shutdown) go func() { <-sigCh + // Restore default signal handling after the first signal so a second + // SIGINT/SIGTERM force-quits a hung shutdown instead of being + // swallowed by this one-shot handler. + signal.Stop(sigCh) + signal.Reset(syscall.SIGINT, syscall.SIGTERM) g.RequestShutdown("signal") }() if err := server.ListenAndServe(g.HandleSessionOnOwner); err != nil { - log.Fatalf("server error: %v", err) + // A transport failure (for example a fatal Accept error) is not a + // clean shutdown: start the full shutdown sequence and WAIT for it, + // so online state is saved and outstanding wagers are refunded before + // exiting non-zero. RequestShutdown is idempotent, so a failure during + // a real shutdown just waits on the already-running sequence. + log.Printf("server error: %v", err) + g.RequestShutdown("transport failure") + select { + case <-g.ShutdownDone(): + case <-time.After(30 * time.Second): + log.Printf("shutdown: timed out waiting for the shutdown sequence to finish") + } + if serr := g.ShutdownSaveError(); serr != nil { + log.Fatalf("shutdown: final save failed: %v", serr) + } + os.Exit(1) } // The transports close inside the shutdown sequence, so a clean return // from ListenAndServe is where the final save is known to have finished. |
