diff options
Diffstat (limited to 'internal/net')
| -rw-r--r-- | internal/net/server.go | 45 | ||||
| -rw-r--r-- | internal/net/server_test.go | 64 |
2 files changed, 105 insertions, 4 deletions
diff --git a/internal/net/server.go b/internal/net/server.go index b2cc3be..6626592 100644 --- a/internal/net/server.go +++ b/internal/net/server.go @@ -6,6 +6,7 @@ import ( "log" "net" "net/http" + "sync" "thehouseoficarus/internal/action" "thehouseoficarus/internal/config" @@ -59,6 +60,7 @@ type Session struct { Disconnecting bool DisconnectTicks int Shop *action.ShopConfig + writeMu sync.Mutex } @@ -74,6 +76,7 @@ type Server struct { } type Hub struct { + mu sync.Mutex sessions map[*Session]bool rooms map[int]map[*Session]bool onRemove func(*Session) @@ -87,23 +90,35 @@ func NewHub() *Hub { } func (h *Hub) OnRemove(cb func(*Session)) { + h.mu.Lock() + defer h.mu.Unlock() h.onRemove = cb } func (h *Hub) Add(s *Session) { + h.mu.Lock() + defer h.mu.Unlock() h.sessions[s] = true } func (h *Hub) Remove(s *Session) { + h.mu.Lock() + defer h.mu.Unlock() if s.Player != nil && s.State == StateGame && !s.Disconnecting { s.Disconnecting = true s.DisconnectTicks = 10 return } - h.HardRemove(s) + h.hardRemoveLocked(s) } func (h *Hub) HardRemove(s *Session) { + h.mu.Lock() + defer h.mu.Unlock() + h.hardRemoveLocked(s) +} + +func (h *Hub) hardRemoveLocked(s *Session) { delete(h.sessions, s) for _, room := range h.rooms { delete(room, s) @@ -114,7 +129,9 @@ func (h *Hub) HardRemove(s *Session) { } func (h *Hub) EnterRoom(s *Session, roomID int) { - h.LeaveRoom(s) + h.mu.Lock() + defer h.mu.Unlock() + h.leaveRoomLocked(s) if h.rooms[roomID] == nil { h.rooms[roomID] = make(map[*Session]bool) } @@ -122,12 +139,20 @@ func (h *Hub) EnterRoom(s *Session, roomID int) { } func (h *Hub) LeaveRoom(s *Session) { + h.mu.Lock() + defer h.mu.Unlock() + h.leaveRoomLocked(s) +} + +func (h *Hub) leaveRoomLocked(s *Session) { for _, room := range h.rooms { delete(room, s) } } func (h *Hub) AllSessions() []*Session { + h.mu.Lock() + defer h.mu.Unlock() var out []*Session for s := range h.sessions { out = append(out, s) @@ -136,6 +161,8 @@ func (h *Hub) AllSessions() []*Session { } func (h *Hub) PlayersInRoom(roomID int) []*Session { + h.mu.Lock() + defer h.mu.Unlock() var out []*Session if room, ok := h.rooms[roomID]; ok { for s := range room { @@ -303,23 +330,33 @@ func (s *Server) handleSession(sess *Session, handler func(*Session, string)) { } func (sess *Session) Write(msg string) { + sess.writeMu.Lock() + defer sess.writeMu.Unlock() sess.Conn.Write([]byte(msg)) } func (sess *Session) WriteLine(msg string) { + sess.writeMu.Lock() + defer sess.writeMu.Unlock() sess.Conn.Write([]byte(msg + "\r\n")) } func (sess *Session) WriteLines(lines ...string) { + sess.writeMu.Lock() + defer sess.writeMu.Unlock() for _, l := range lines { - sess.WriteLine(l) + sess.Conn.Write([]byte(l + "\r\n")) } } func (sess *Session) Writef(format string, args ...interface{}) { - sess.Write(fmt.Sprintf(format, args...)) + sess.writeMu.Lock() + defer sess.writeMu.Unlock() + sess.Conn.Write([]byte(fmt.Sprintf(format, args...))) } func (sess *Session) Close() error { + sess.writeMu.Lock() + defer sess.writeMu.Unlock() return sess.Conn.Close() } diff --git a/internal/net/server_test.go b/internal/net/server_test.go new file mode 100644 index 0000000..fca5528 --- /dev/null +++ b/internal/net/server_test.go @@ -0,0 +1,64 @@ +package net + +import ( + "sync" + "testing" +) + +func TestHubConcurrency(t *testing.T) { + h := NewHub() + h.OnRemove(func(s *Session) {}) + + var wg sync.WaitGroup + const n = 50 + + for i := 0; i < n; i++ { + wg.Add(1) + go func() { + defer wg.Done() + s := &Session{} + h.Add(s) + h.EnterRoom(s, 1) + h.PlayersInRoom(1) + h.LeaveRoom(s) + h.HardRemove(s) + }() + } + + for i := 0; i < n; i++ { + wg.Add(1) + go func() { + defer wg.Done() + h.AllSessions() + }() + } + + wg.Wait() +} + +type testConn struct{ data []byte } + +func (c *testConn) ReadMessage() (string, error) { return "", nil } +func (c *testConn) Write(b []byte) (int, error) { c.data = append(c.data, b...); return len(b), nil } +func (c *testConn) Close() error { return nil } +func (c *testConn) SetEcho(bool) error { return nil } + +func TestSessionWriteConcurrency(t *testing.T) { + conn := &testConn{} + sess := &Session{Conn: conn} + + var wg sync.WaitGroup + const n = 50 + + for i := 0; i < n; i++ { + wg.Add(1) + go func() { + defer wg.Done() + sess.Write("hello") + sess.WriteLine("world") + sess.Writef("formatted %d", 42) + }() + } + + wg.Wait() +} |
