package game // Disconnect-cleanup regressions: DisconnectTick runs on the owner // goroutine, so the Hub removal callback it triggers must execute inline // rather than re-submitting through run() — which would deadlock waiting // for the running tick to finish (freezing every tick afterward). import ( "testing" "time" "thehouseoficarus/internal/net" ) // TestDisconnectTickHardRemoveCompletesOnOwner guards the single-owner // re-entrancy fix: DisconnectTick (owner) -> Hub.HardRemove -> onRemove // callback must not block on the owner from the owner. func TestDisconnectTickHardRemoveCompletesOnOwner(t *testing.T) { h := newHarness(t) h.seedAccount("alice") h.seedCharacter("alice", "alice") sess, _ := h.connect("alice") done := make(chan struct{}) h.g.RunOnOwner(func() { sess.StartDisconnect(1) }) h.barrier() go func() { h.g.RunOnOwner(func() { h.g.DisconnectTick() }) close(done) }() select { case <-done: case <-time.After(5 * time.Second): t.Fatal("DisconnectTick deadlocked: Hub.HardRemove's onRemove callback re-submitted to the owner from the owner") } // The session was hard-removed and its character persisted. if sessions := h.g.Hub.AllSessions(); len(sessions) != 0 { t.Fatalf("session still in hub after disconnect tick: %d", len(sessions)) } if _, err := h.g.AccountStore.LoadCharacter("alice"); err != nil { t.Fatalf("character file unreadable after disconnect save: %v", err) } } // TestHubRemoveNilPlayerOnOwner: DisconnectTick removes sessions without a // loaded player through Hub.Remove, which runs the same onRemove callback. func TestHubRemoveNilPlayerOnOwner(t *testing.T) { h := newHarness(t) sess := &net.Session{Conn: newChanConn()} sess.SetState(net.StateAccountName) sess.StartWriter() h.hub.Add(sess) done := make(chan struct{}) go func() { h.g.RunOnOwner(func() { h.g.Hub.Remove(sess) }) close(done) }() select { case <-done: case <-time.After(5 * time.Second): t.Fatal("Hub.Remove with a nil player deadlocked on the owner") } if sessions := h.g.Hub.AllSessions(); len(sessions) != 0 { t.Fatalf("player-less session still in hub: %d", len(sessions)) } }