diff options
| author | historia <[not public]> | 2026-09-18 15:16:44 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-18 15:16:44 -0400 |
| commit | 7eab582d8a8ee2af1a6baae7baf0696c7005de2e (patch) | |
| tree | 902bbc8c6e39c9f62f779ca23267714ec420b9f5 /cmd/thoi | |
| parent | 153acb7f644d755c45533b7c5311e3ffa47e8973 (diff) | |
| download | thehouseoficarus-7eab582d8a8ee2af1a6baae7baf0696c7005de2e.tar.gz | |
fix: harden triggers, economy, casino, world data, and admin/net paths
Diffstat (limited to 'cmd/thoi')
| -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. |
