diff options
Diffstat (limited to 'internal/game/cmd_shutdown.go')
| -rw-r--r-- | internal/game/cmd_shutdown.go | 115 |
1 files changed, 63 insertions, 52 deletions
diff --git a/internal/game/cmd_shutdown.go b/internal/game/cmd_shutdown.go index 0bb0f8f..0030649 100644 --- a/internal/game/cmd_shutdown.go +++ b/internal/game/cmd_shutdown.go @@ -1,14 +1,24 @@ package game +// The shutdown command schedules a graceful server shutdown. Signals and +// this command converge on Game.RequestShutdown, so both paths drain work, +// resolve outstanding wagers, and save owned state before transports close. + import ( "fmt" - "os" "strconv" "time" "thehouseoficarus/internal/net" ) +// scheduledShutdown carries one countdown run's captured cancel channel, so +// a later run (or cancel) can never close or read a replacement run's +// channel. +type scheduledShutdown struct { + cancel chan struct{} +} + func (g *Game) executeShutdown(sess *net.Session, args []string, rawInput string) { if !g.checkAdmin(sess) { sess.WriteLine("Unknown command.") @@ -21,20 +31,17 @@ func (g *Game) executeShutdown(sess *net.Session, args []string, rawInput string if args[0] == "cancel" { g.shutdownMu.Lock() - if !g.shutdownActive { - g.shutdownMu.Unlock() + run := g.shutdownRun + g.shutdownMu.Unlock() + if run == nil { sess.WriteLine("No shutdown in progress.") return } - close(g.shutdownCancel) - g.shutdownActive = false + close(run.cancel) + g.shutdownMu.Lock() + g.shutdownRun = nil g.shutdownMu.Unlock() - - for _, other := range g.Hub.AllSessions() { - if other.Player != nil { - other.WriteLine("[Server] Shutdown cancelled.") - } - } + g.broadcastLoggedIn("[Server] Shutdown cancelled.") return } @@ -45,58 +52,62 @@ func (g *Game) executeShutdown(sess *net.Session, args []string, rawInput string } g.shutdownMu.Lock() - if g.shutdownActive { + if g.shutdownRun != nil { g.shutdownMu.Unlock() sess.WriteLine("A shutdown is already in progress.") return } - g.shutdownActive = true - g.shutdownCancel = make(chan struct{}) + run := &scheduledShutdown{cancel: make(chan struct{})} + g.shutdownRun = run g.shutdownMu.Unlock() - go func() { - remaining := minutes * 60 - ticker := time.NewTicker(30 * time.Second) - defer ticker.Stop() + go g.runShutdownCountdown(run, minutes) + sess.WriteLine(fmt.Sprintf("Shutdown scheduled in %d minute(s).", minutes)) +} - for _, other := range g.Hub.AllSessions() { - if other.Player != nil { - other.WriteLine(fmt.Sprintf("[Server] Shutting down in %d minute(s).", minutes)) - } - } +// runShutdownCountdown announces the countdown and requests the shutdown +// at expiry. Cancellation is handled through the run's captured channel; +// nothing reads a replaceable shared field here. +func (g *Game) runShutdownCountdown(run *scheduledShutdown, minutes int) { + remaining := minutes * 60 + ticker := time.NewTicker(30 * time.Second) + defer ticker.Stop() - for { - select { - case <-ticker.C: - remaining -= 30 - if remaining <= 0 { - for _, other := range g.Hub.AllSessions() { - if other.Player != nil { - other.WriteLine("[Server] Shutting down NOW.") - } - } - os.Exit(0) - } - mins := remaining / 60 - secs := remaining % 60 - if secs == 0 { - for _, other := range g.Hub.AllSessions() { - if other.Player != nil { - other.WriteLine(fmt.Sprintf("[Server] Shutting down in %d minute(s).", mins)) - } - } - } else { - for _, other := range g.Hub.AllSessions() { - if other.Player != nil { - other.WriteLine(fmt.Sprintf("[Server] Shutting down in %d minute(s) %d seconds.", mins, secs)) - } - } + g.broadcastLoggedIn(fmt.Sprintf("[Server] Shutting down in %d minute(s).", minutes)) + + for { + select { + case <-ticker.C: + remaining -= 30 + if remaining <= 0 { + g.broadcastLoggedIn("[Server] Shutting down NOW.") + if g.shutdownHook != nil { + g.shutdownHook("scheduled shutdown") } - case <-g.shutdownCancel: return } + mins := remaining / 60 + secs := remaining % 60 + if secs == 0 { + g.broadcastLoggedIn(fmt.Sprintf("[Server] Shutting down in %d minute(s).", mins)) + } else { + g.broadcastLoggedIn(fmt.Sprintf("[Server] Shutting down in %d minute(s) %d seconds.", mins, secs)) + } + case <-run.cancel: + return } - }() + } +} - sess.WriteLine(fmt.Sprintf("Shutdown scheduled in %d minute(s).", minutes)) +// broadcastLoggedIn writes one line to every logged-in player. Read-only +// over Hub membership (mutex-protected); writes go through outbound queues. +func (g *Game) broadcastLoggedIn(msg string) { + if g.Hub == nil { + return + } + for _, other := range g.Hub.AllSessions() { + if other.Player != nil { + other.WriteLine(msg) + } + } } |
