diff options
| author | historia <[not public]> | 2026-09-16 13:52:58 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-16 13:52:58 -0400 |
| commit | c054b27f628f23fecc505b9e09e78721738f3c4e (patch) | |
| tree | ea197165bc454ee811dd22b9ceef5dc94bb0b279 /internal/game/game.go | |
| parent | f2d44f10f52d3fe68fdb19b518a330f7af9e1ed2 (diff) | |
| download | thehouseoficarus-c054b27f628f23fecc505b9e09e78721738f3c4e.tar.gz | |
fix: shutdown admission, recovery identity/order, rollback journal retention, pickup merge
Diffstat (limited to 'internal/game/game.go')
| -rw-r--r-- | internal/game/game.go | 28 |
1 files changed, 24 insertions, 4 deletions
diff --git a/internal/game/game.go b/internal/game/game.go index 7f29d1f..ca38e27 100644 --- a/internal/game/game.go +++ b/internal/game/game.go @@ -135,6 +135,15 @@ func New(dataDir string, colorConfig *config.ColorsConfig, constantColorConfig * // initialization cycle with the shutdown coordinator. g.shutdownHook = g.RequestShutdown g.Economy = newEconomyJournal(dataDir) + // Lifecycle-journal rollback MUST run before economy recovery: a rolled- + // back rename transaction restores an older character before-image, and + // if money compensation ran first its settled/compacted records would no + // longer be recoverable — the rollback would silently erase a payout + // that was already re-applied. Rollback also precedes every store read + // below (courses, mods, techs, craft index) that a leftover transaction + // may reference. See the TestEconomyRecoveryRunsAfterLifecycleRollback + // regression. + g.recoverJournals() // Compensate open money records from any prior crash before any // session can connect. g.recoverEconomy() @@ -144,9 +153,7 @@ func New(dataDir string, colorConfig *config.ColorsConfig, constantColorConfig * g.LoadTechs() g.buildCraftIndex() g.loadAllFlagTriggers() - // Journal recovery before any store reads files a leftover transaction - // may reference, then one-time stable-identity migration. - g.recoverJournals() + // One-time stable-identity migration. g.backfillCharacterIDs() g.ValidateAndLog() return g @@ -273,9 +280,22 @@ func (g *Game) saveAllOnlineCharacters() error { // HandleSessionOnOwner is the entry point handed to the network server: // connection goroutines submit one task per input line, so all game state -// mutation executes on the owner goroutine. +// mutation executes on the owner goroutine. Once shutdown has begun, new +// input is REJECTED (not queued and not executed inline): the shutdown +// sequence owns final persistence, and the transports keep draining until +// they close, so an accepted line could mutate characters after the final +// save or race the refund/save barrier. func (g *Game) HandleSessionOnOwner(sess *net.Session, input string) { + if g.isShuttingDown() { + return + } g.RunOnOwner(func() { + // Re-checked at task execution: the flag may flip between + // submission and execution, and RunOnOwner must never fall back + // to inline execution after the owner has stopped. + if g.isShuttingDown() { + return + } g.HandleSession(sess, input) }) } |
