fix: watch together server races, reconnect readiness, session end

This commit is contained in:
edde746
2026-04-16 15:24:44 +02:00
parent 1d3ae3e1c5
commit 34815f22ee
6 changed files with 117 additions and 67 deletions
+33 -29
View File
@@ -202,10 +202,11 @@ type serverMsg struct {
type Client struct {
conn *websocket.Conn
send chan []byte
done chan struct{}
}
func newClient(conn *websocket.Conn) *Client {
c := &Client{conn: conn, send: make(chan []byte, 64)}
c := &Client{conn: conn, send: make(chan []byte, 64), done: make(chan struct{})}
go c.writePump()
return c
}
@@ -215,13 +216,12 @@ func (c *Client) writePump() {
defer ticker.Stop()
for {
select {
case data, ok := <-c.send:
if !ok {
c.conn.WriteMessage(websocket.CloseMessage, nil)
return
}
case data := <-c.send:
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
c.conn.WriteMessage(websocket.TextMessage, data)
case <-c.done:
c.conn.WriteMessage(websocket.CloseMessage, nil)
return
case <-ticker.C:
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
@@ -231,20 +231,24 @@ func (c *Client) writePump() {
}
}
func (c *Client) trySend(data []byte) {
select {
case c.send <- data:
case <-c.done:
default:
}
}
func (c *Client) sendJSON(msg serverMsg) {
data, err := json.Marshal(msg)
if err != nil {
return
}
select {
case c.send <- data:
default:
// Drop if buffer full (slow client)
}
c.trySend(data)
}
func (c *Client) close() {
close(c.send)
close(c.done)
}
// --- Room ---
@@ -281,10 +285,7 @@ func (r *Room) broadcastExcept(senderID string, msg serverMsg) {
r.mu.RUnlock()
for _, client := range targets {
select {
case client.send <- data:
default:
}
client.trySend(data)
}
}
@@ -299,12 +300,8 @@ func (r *Room) sendTo(targetID string, msg serverMsg) bool {
if !ok {
return false
}
select {
case client.send <- data:
return true
default:
return false
}
client.trySend(data)
return true
}
// --- Log store ---
@@ -556,20 +553,27 @@ func (s *Server) handleWS(w http.ResponseWriter, r *http.Request) {
var currentPeerID string
var isHost bool
// Cleanup on disconnect
// Cleanup on disconnect — only if our Client is still the one in the room.
// A reconnecting peer reuses the same peerId, so the map entry may have
// been overwritten by a newer Client before this defer runs.
defer func() {
if currentRoom != nil && currentPeerID != "" {
currentRoom.mu.Lock()
delete(currentRoom.Peers, currentPeerID)
stale := currentRoom.Peers[currentPeerID] != client
if !stale {
delete(currentRoom.Peers, currentPeerID)
}
currentRoom.mu.Unlock()
currentRoom.broadcastExcept(currentPeerID, serverMsg{
Type: "peerLeft",
PeerID: currentPeerID,
})
if !stale {
currentRoom.broadcastExcept(currentPeerID, serverMsg{
Type: "peerLeft",
PeerID: currentPeerID,
})
}
if isHost {
s.conns.releaseRoom(ip)
}
log.Printf("peer %s left room %s", currentPeerID, currentRoom.SessionID)
log.Printf("peer %s left room %s (stale=%v)", currentPeerID, currentRoom.SessionID, stale)
}
}()