aboutsummaryrefslogtreecommitdiff
path: root/internal/game/cmd_shutdown.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/cmd_shutdown.go')
-rw-r--r--internal/game/cmd_shutdown.go115
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)
+ }
+ }
}