aboutsummaryrefslogtreecommitdiff
path: root/internal/net
diff options
context:
space:
mode:
Diffstat (limited to 'internal/net')
-rw-r--r--internal/net/server.go45
-rw-r--r--internal/net/server_test.go64
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()
+}