aboutsummaryrefslogtreecommitdiff
path: root/internal/net
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-18 01:50:17 -0400
committerhistoria <[not public]>2026-09-18 01:50:17 -0400
commit87d08f0151e643951769e323995f1c5ed89ee200 (patch)
treedc42a0b30ebb5a00dd7ba505cc4eaa44f4f308bc /internal/net
parent32a3c59cebf2a16277e54d715a67adfdbe00b456 (diff)
downloadthehouseoficarus-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.go27
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