mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 23:54:33 +01:00
The global stagesLock sync.RWMutex protected map[string]*Stage, causing all stage operations to contend on a single lock even for unrelated stages. Any stage creation or deletion blocked all reads server-wide. Replace with a typed StageMap wrapper around sync.Map which provides lock-free reads and allows concurrent writes to disjoint keys. Per-stage sync.RWMutex remains unchanged for protecting individual stage state. StageMap exposes Get, GetOrCreate, StoreIfAbsent, Store, Delete, and Range methods. Updated ~50 call sites across 6 production files and 9 test files.
187 lines
5.0 KiB
Go
187 lines
5.0 KiB
Go
package channelserver
|
|
|
|
import (
|
|
"net"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
func createTestChannels(count int) []*Server {
|
|
channels := make([]*Server, count)
|
|
for i := 0; i < count; i++ {
|
|
s := createTestServer()
|
|
s.ID = uint16(0x1010 + i)
|
|
s.IP = "10.0.0.1"
|
|
s.Port = uint16(54001 + i)
|
|
s.GlobalID = "0101"
|
|
s.userBinary = NewUserBinaryStore()
|
|
channels[i] = s
|
|
}
|
|
return channels
|
|
}
|
|
|
|
func TestLocalRegistryFindSessionByCharID(t *testing.T) {
|
|
channels := createTestChannels(2)
|
|
reg := NewLocalChannelRegistry(channels)
|
|
|
|
conn1 := &mockConn{}
|
|
sess1 := createTestSessionForServer(channels[0], conn1, 100, "Alice")
|
|
channels[0].Lock()
|
|
channels[0].sessions[conn1] = sess1
|
|
channels[0].Unlock()
|
|
|
|
conn2 := &mockConn{}
|
|
sess2 := createTestSessionForServer(channels[1], conn2, 200, "Bob")
|
|
channels[1].Lock()
|
|
channels[1].sessions[conn2] = sess2
|
|
channels[1].Unlock()
|
|
|
|
// Find on first channel
|
|
found := reg.FindSessionByCharID(100)
|
|
if found == nil || found.charID != 100 {
|
|
t.Errorf("FindSessionByCharID(100) = %v, want session with charID 100", found)
|
|
}
|
|
|
|
// Find on second channel
|
|
found = reg.FindSessionByCharID(200)
|
|
if found == nil || found.charID != 200 {
|
|
t.Errorf("FindSessionByCharID(200) = %v, want session with charID 200", found)
|
|
}
|
|
|
|
// Not found
|
|
found = reg.FindSessionByCharID(999)
|
|
if found != nil {
|
|
t.Errorf("FindSessionByCharID(999) = %v, want nil", found)
|
|
}
|
|
}
|
|
|
|
func TestLocalRegistryFindChannelForStage(t *testing.T) {
|
|
channels := createTestChannels(2)
|
|
channels[0].GlobalID = "0101"
|
|
channels[1].GlobalID = "0102"
|
|
reg := NewLocalChannelRegistry(channels)
|
|
|
|
channels[1].stages.Store("sl2Qs123p0a0u42", NewStage("sl2Qs123p0a0u42"))
|
|
|
|
gid := reg.FindChannelForStage("u42")
|
|
if gid != "0102" {
|
|
t.Errorf("FindChannelForStage(u42) = %q, want %q", gid, "0102")
|
|
}
|
|
|
|
gid = reg.FindChannelForStage("u999")
|
|
if gid != "" {
|
|
t.Errorf("FindChannelForStage(u999) = %q, want empty", gid)
|
|
}
|
|
}
|
|
|
|
func TestLocalRegistryDisconnectUser(t *testing.T) {
|
|
channels := createTestChannels(1)
|
|
reg := NewLocalChannelRegistry(channels)
|
|
|
|
conn := &mockConn{}
|
|
sess := createTestSessionForServer(channels[0], conn, 42, "Target")
|
|
channels[0].Lock()
|
|
channels[0].sessions[conn] = sess
|
|
channels[0].Unlock()
|
|
|
|
reg.DisconnectUser([]uint32{42})
|
|
|
|
if !conn.WasClosed() {
|
|
t.Error("DisconnectUser should have closed the connection for charID 42")
|
|
}
|
|
}
|
|
|
|
func TestLocalRegistrySearchSessions(t *testing.T) {
|
|
channels := createTestChannels(2)
|
|
reg := NewLocalChannelRegistry(channels)
|
|
|
|
// Add 3 sessions across 2 channels
|
|
for i, ch := range channels {
|
|
conn := &mockConn{}
|
|
sess := createTestSessionForServer(ch, conn, uint32(i+1), "Player")
|
|
sess.stage = NewStage("sl1Ns200p0a0u0")
|
|
ch.Lock()
|
|
ch.sessions[conn] = sess
|
|
ch.Unlock()
|
|
}
|
|
conn3 := &mockConn{}
|
|
sess3 := createTestSessionForServer(channels[0], conn3, 3, "Player")
|
|
sess3.stage = NewStage("sl1Ns200p0a0u0")
|
|
channels[0].Lock()
|
|
channels[0].sessions[conn3] = sess3
|
|
channels[0].Unlock()
|
|
|
|
// Search all
|
|
results := reg.SearchSessions(func(s SessionSnapshot) bool { return true }, 10)
|
|
if len(results) != 3 {
|
|
t.Errorf("SearchSessions(all) returned %d results, want 3", len(results))
|
|
}
|
|
|
|
// Search with max
|
|
results = reg.SearchSessions(func(s SessionSnapshot) bool { return true }, 2)
|
|
if len(results) != 2 {
|
|
t.Errorf("SearchSessions(max=2) returned %d results, want 2", len(results))
|
|
}
|
|
|
|
// Search with predicate
|
|
results = reg.SearchSessions(func(s SessionSnapshot) bool { return s.CharID == 1 }, 10)
|
|
if len(results) != 1 {
|
|
t.Errorf("SearchSessions(charID==1) returned %d results, want 1", len(results))
|
|
}
|
|
}
|
|
|
|
func TestLocalRegistrySearchStages(t *testing.T) {
|
|
channels := createTestChannels(1)
|
|
reg := NewLocalChannelRegistry(channels)
|
|
|
|
channels[0].stages.Store("sl2Ls210test1", NewStage("sl2Ls210test1"))
|
|
channels[0].stages.Store("sl2Ls210test2", NewStage("sl2Ls210test2"))
|
|
channels[0].stages.Store("sl1Ns200other", NewStage("sl1Ns200other"))
|
|
|
|
results := reg.SearchStages("sl2Ls210", 10)
|
|
if len(results) != 2 {
|
|
t.Errorf("SearchStages(sl2Ls210) returned %d results, want 2", len(results))
|
|
}
|
|
|
|
results = reg.SearchStages("sl2Ls210", 1)
|
|
if len(results) != 1 {
|
|
t.Errorf("SearchStages(sl2Ls210, max=1) returned %d results, want 1", len(results))
|
|
}
|
|
}
|
|
|
|
func TestLocalRegistryConcurrentAccess(t *testing.T) {
|
|
channels := createTestChannels(2)
|
|
reg := NewLocalChannelRegistry(channels)
|
|
|
|
// Populate some sessions
|
|
for _, ch := range channels {
|
|
for i := 0; i < 10; i++ {
|
|
conn := &mockConn{remoteAddr: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 50000 + i}}
|
|
sess := createTestSessionForServer(ch, conn, uint32(i+1), "Player")
|
|
sess.stage = NewStage("sl1Ns200p0a0u0")
|
|
ch.Lock()
|
|
ch.sessions[conn] = sess
|
|
ch.Unlock()
|
|
}
|
|
}
|
|
|
|
// Run concurrent operations
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < 50; i++ {
|
|
wg.Add(3)
|
|
go func(id int) {
|
|
defer wg.Done()
|
|
_ = reg.FindSessionByCharID(uint32(id%10 + 1))
|
|
}(i)
|
|
go func() {
|
|
defer wg.Done()
|
|
_ = reg.FindChannelForStage("u0")
|
|
}()
|
|
go func() {
|
|
defer wg.Done()
|
|
_ = reg.SearchSessions(func(s SessionSnapshot) bool { return true }, 5)
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
}
|