diff options
| author | historia <[not public]> | 2026-09-18 01:50:17 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-18 01:50:17 -0400 |
| commit | 87d08f0151e643951769e323995f1c5ed89ee200 (patch) | |
| tree | dc42a0b30ebb5a00dd7ba505cc4eaa44f4f308bc /internal/net | |
| parent | 32a3c59cebf2a16277e54d715a67adfdbe00b456 (diff) | |
| download | thehouseoficarus-87d08f0151e643951769e323995f1c5ed89ee200.tar.gz | |
fix: harden economy recovery, shutdown concurrency, world validation, and inventory-loss paths
Diffstat (limited to 'internal/net')
| -rw-r--r-- | internal/net/server.go | 27 |
1 files changed, 27 insertions, 0 deletions
diff --git a/internal/net/server.go b/internal/net/server.go index 78eceb8..9d99acc 100644 --- a/internal/net/server.go +++ b/internal/net/server.go @@ -311,6 +311,16 @@ func (h *Hub) Add(s *Session) { h.sessions[s] = true } +// SetSessionPlayer assigns a session's live-character pointer while holding the +// Hub mutex. The owner goroutine writes Player here; Hub.Remove (a connection +// goroutine) reads it under the same mutex, so the pointer is synchronized +// instead of a data race. +func (h *Hub) SetSessionPlayer(s *Session, p *player.Player) { + h.mu.Lock() + s.Player = p + h.mu.Unlock() +} + func (h *Hub) Remove(s *Session) { h.mu.Lock() if s.Player != nil && !s.IsDisconnecting() { @@ -592,6 +602,23 @@ func (s *Server) Shutdown() { for _, sess := range s.hub.AllSessions() { sess.Close() } + // Give each writer goroutine a bounded window to flush its outbound + // queue before the process exits; otherwise shutdown and refund + // messages can be truncated for slower clients. + flushDeadline := time.Now().Add(2 * time.Second) + for _, sess := range s.hub.AllSessions() { + if sess.writeDone == nil { + continue + } + remaining := time.Until(flushDeadline) + if remaining <= 0 { + break + } + select { + case <-sess.writeDone: + case <-time.After(remaining): + } + } s.finish(nil) }) <-s.done |
