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 /internal/game/game.go | |
| parent | 153acb7f644d755c45533b7c5311e3ffa47e8973 (diff) | |
| download | thehouseoficarus-7eab582d8a8ee2af1a6baae7baf0696c7005de2e.tar.gz | |
fix: harden triggers, economy, casino, world data, and admin/net paths
Diffstat (limited to 'internal/game/game.go')
| -rw-r--r-- | internal/game/game.go | 18 |
1 files changed, 18 insertions, 0 deletions
diff --git a/internal/game/game.go b/internal/game/game.go index de383a6..3ffc72e 100644 --- a/internal/game/game.go +++ b/internal/game/game.go @@ -87,6 +87,11 @@ type Game struct { shutdownRun *scheduledShutdown shutdownHook func(reason string) transportStop func() + // shutdownDone is closed after runShutdown's ordered sequence (drain, + // refund, final save, transport close) completes. main waits on it on the + // transport-failure path before reading ShutdownSaveError, so it cannot + // exit while the final save is still in flight. + shutdownDone chan struct{} // shutdownSaveMu guards shutdownSaveErr, the error of the final save // runShutdown performs (nil when it succeeded or no shutdown ran). main @@ -140,6 +145,7 @@ func New(dataDir string, colorConfig *config.ColorsConfig, constantColorConfig * guardWatchTimers: make(map[string]int), hackingStates: make(map[string]*hacking.Session), sequences: make(map[string]*sequence), + shutdownDone: make(chan struct{}), } g.owner = newOwner() // Runtime assignment (not initializer-level) so the command registry, @@ -250,6 +256,15 @@ func (g *Game) ShutdownSaveError() error { return g.shutdownSaveErr } +// ShutdownDone returns a channel that is closed once the graceful shutdown +// sequence (including the final online-character save and the transport +// close) has completed. A caller that starts shutdown itself (the +// transport-failure path) waits on it before reading ShutdownSaveError and +// exiting, so the save is not skipped. +func (g *Game) ShutdownDone() <-chan struct{} { + return g.shutdownDone +} + // recordShutdownSaveErr stores the final-save failure for ShutdownSaveError. // The shutdown sequence runs on its own goroutine, so the field is mutex // guarded against the reader in main. @@ -300,6 +315,9 @@ func (g *Game) runShutdown() { g.transportStop() } log.Println("shutdown complete") + // Publish completion last: main waits on this before reading + // ShutdownSaveError on the transport-failure path. + close(g.shutdownDone) } // saveAllOnlineCharacters persists every online player exactly once under |
