Skip to content

Fix WebSocket TOCTOU race conditions and concurrent write panics (#402) - #461

Open
dhruvv16-hash wants to merge 1 commit into
AOSSIE-Org:mainfrom
dhruvv16-hash:fix-websocket-toctou-race
Open

Fix WebSocket TOCTOU race conditions and concurrent write panics (#402)#461
dhruvv16-hash wants to merge 1 commit into
AOSSIE-Org:mainfrom
dhruvv16-hash:fix-websocket-toctou-race

Conversation

@dhruvv16-hash

@dhruvv16-hash dhruvv16-hash commented Sep 9, 2026

Copy link
Copy Markdown

Hey everyone! 👋

The Problem:
This PR resolves several critical concurrency bugs in the WebSocket handling:

  1. TOCTOU Ghost Rooms: In both websocket.go and team_websocket.go, a Time-Of-Check to Time-Of-Use race condition existed where a client joining a room could lock it and add themselves right after the last client left and triggered a room deletion. This left new clients stranded in an orphaned "ghost" room structure that was removed from the main map.
  2. Concurrent Write Panics: Gorillas WriteJSON and WriteMessage functions are not safe for concurrent use. There were several locations in websocket.go, debate_spectator.go, and gamification_handler.go where raw conn.WriteJSON or conn.WriteMessage were invoked simultaneously alongside the broadcast goroutines invoking SafeWriteJSON, causing panics.

The Fix:

  • Addressed the TOCTOU race by consistently locking roomsMutex before checking room.Mutex when evaluating room deletion in the disconnect loops.
  • Moved upgrader.Upgrade outside of the critical map lookup section for safety.
  • Replaced all unprotected conn.WriteJSON and conn.WriteMessage occurrences with safe wrappers (client.SafeWriteJSON or client.WriteJSON) that utilize the client's dedicated writeMu mutex lock.

Fixes #402

Summary by CodeRabbit

  • Bug Fixes
    • Improved WebSocket connection stability during spectator, team, and debate sessions.
    • Prevented connection setup and cleanup issues when multiple participants join or leave rooms simultaneously.
    • Improved reliability of real-time updates, including presence, poll snapshots, automatic muting, and keepalive responses.
    • Reduced the risk of missed or overlapping messages during active sessions.

Copilot AI lite review requested due to automatic review settings September 9, 2026 20:07
@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 1c0fd687-54ed-400e-b7ed-aecf061c7cb0

📥 Commits

Reviewing files that changed from the base of the PR and between 5b1167c and 691dbd9.

📒 Files selected for processing (5)
  • backend/websocket/debate_spectator.go
  • backend/websocket/gamification.go
  • backend/websocket/gamification_handler.go
  • backend/websocket/team_websocket.go
  • backend/websocket/websocket.go

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The patch synchronizes initial and control WebSocket writes. It also changes room admission and disconnect cleanup locking so room membership checks and registry updates occur under coordinated locks.

Changes

WebSocket concurrency

Layer / File(s) Summary
Mutex-protected client writes
backend/websocket/gamification.go, backend/websocket/gamification_handler.go, backend/websocket/debate_spectator.go, backend/websocket/websocket.go
Adds GamificationClient.SafeWriteMessage and routes pong, spectator initialization, and phase-change responses through mutex-protected write methods.
Locked room admission
backend/websocket/websocket.go, backend/websocket/team_websocket.go
Moves connection upgrades before room locking and holds the room mutex through assignment validation and client registration.
Rechecked room cleanup
backend/websocket/websocket.go, backend/websocket/team_websocket.go
Releases locks in order and rechecks room emptiness before deleting a room from the room registry.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 691db

This change serializes WebSocket writes and room membership updates, preventing concurrent write failures and incorrect room admission. No concrete current-head merge-blocking risk remains.

Sequence Diagram(s)

sequenceDiagram
  participant WebsocketHandler
  participant roomsMutex
  participant roomMutex
  participant websocketUpgrader
  participant roomClients
  WebsocketHandler->>websocketUpgrader: Upgrade connection
  WebsocketHandler->>roomsMutex: Lock room registry
  WebsocketHandler->>roomMutex: Lock selected room
  WebsocketHandler->>roomClients: Validate assignment and register client
  WebsocketHandler->>roomMutex: Unlock selected room
  WebsocketHandler->>roomsMutex: Unlock room registry
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the two main changes: WebSocket TOCTOU race fixes and prevention of concurrent write panics.
Linked Issues check ✅ Passed The changes satisfy issue [#402]. The room mutex now protects the debater capacity check through client registration, preventing concurrent connections from exceeding the two-debater limit.
Out of Scope Changes check ✅ Passed The additional synchronized write changes and related room-locking fixes directly support the PR objectives for WebSocket concurrency safety. No unrelated changes are evident.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 5 files.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

TeamWebsocketHandler currently attempts to write an HTTP JSON error response after the WebSocket upgrade, which is unreliable/incorrect once the connection is upgraded.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR addresses concurrency hazards in the backend WebSocket layer by tightening room lifecycle locking to prevent TOCTOU “ghost rooms” and by routing WebSocket writes through per-connection mutex-protected helpers to avoid concurrent write panics.

Changes:

  • Reordered locking in room join/leave paths to ensure room deletion checks are performed safely under consistent mutex ordering.
  • Moved WebSocket upgrades out of shared map critical sections and replaced unsafe raw writes (WriteJSON/WriteMessage) with safe wrappers using per-client write mutexes.
  • Added a safe raw-message writer for gamification WebSockets and applied it to ping/pong handling.
File summaries
File Description
backend/websocket/websocket.go Adjusts room join/disconnect locking to avoid TOCTOU deletion races; switches a remaining direct write to SafeWriteJSON.
backend/websocket/team_websocket.go Adjusts team room creation/join/disconnect locking and moves upgrade earlier in the handler.
backend/websocket/gamification.go Adds SafeWriteMessage to protect raw WebSocket writes with the client write mutex.
backend/websocket/gamification_handler.go Uses SafeWriteMessage for pong responses to avoid concurrent write panics.
backend/websocket/debate_spectator.go Routes initial snapshot/presence writes through the spectator client writer (mutex-protected).
Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 256 to 262
if userTeamIDHex != team1IDHex && userTeamIDHex != team2IDHex {
log.Printf("[TeamWebsocketHandler] ❌ ERROR: UserTeamID %s doesn't match Team1ID %s or Team2ID %s", userTeamIDHex, team1IDHex, team2IDHex)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Team assignment error"})
room.Mutex.Unlock()
conn.Close()
return
}
Comment on lines 640 to +644
"isMuted": shouldBeMuted,
"currentTurn": currentTurn,
"phase": message.Phase,
}
if err := clientConn.WriteJSON(response); err != nil {
if err := client.SafeWriteJSON(response); err != nil {
@gitcordapp

gitcordapp Bot commented Sep 9, 2026

Copy link
Copy Markdown

Link your account with Gitcord

Thanks for opening this PR, @dhruvv16-hash!

To receive Discord notifications and contributor tracking for this organization:

  1. Join Discord: https://discord.gg/hjUhu33uAn
  2. In Discord, run /link dhruvv16-hash
  3. Paste the verification code into your GitHub bio (or a public gist)
  4. Click Verify in Discord (or run /verify-link dhruvv16-hash)

Once linked, Gitcord can notify you about reviews, merges, and more.

Posted by Gitcord

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG]: Race condition allows more than 2 debaters to join a room (TOCTOU in WebsocketHandler)

2 participants