Skip to content

cleanupRoutine that automatically deletes debate rooms that haven't been active for 24 hours.#304

Open
aniket866 wants to merge 3 commits into
AOSSIE-Org:mainfrom
aniket866:fixing-memory-leak
Open

cleanupRoutine that automatically deletes debate rooms that haven't been active for 24 hours.#304
aniket866 wants to merge 3 commits into
AOSSIE-Org:mainfrom
aniket866:fixing-memory-leak

Conversation

@aniket866

@aniket866 aniket866 commented Feb 7, 2026

Copy link
Copy Markdown
  • Fix: Implement a cleanup routine (Ticker) or an LRU cache to delete old rooms. Alternatively, offload storage to Redis/DB and remove from memory.

  • Why: The server will eventually crash with Out Of Memory (OOM) as traffic increases.

What Changed?

  • Added LastActivity: The DebateRoom struct now remembers the last time someone touched it.

  • Added init() and cleanupRoutine(): A background worker now wakes up every 1 hour.

  • Logic in cleanupOldRooms: It checks every room. If a room hasn't been touched in 24 hours, it is deleted from the map, freeing up the memory.

Video or screenshot : N/A logical changes

@bhavik-mangla Closes #303

Summary by CodeRabbit

  • New Features

    • Automatic cleanup of inactive debate rooms after 24 hours of inactivity
  • Improvements

    • Enhanced debate messages now include phase information, content, and timestamps for better tracking and activity monitoring

@coderabbitai

coderabbitai Bot commented Feb 7, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Implemented an automatic room cleanup system in the debate controller to address memory accumulation. Added background cleanup routine with configurable TTL that periodically removes inactive debate rooms. Enhanced DebateMessage and DebateRoom structures with timestamp and activity tracking fields to support the cleanup mechanism.

Changes

Cohort / File(s) Summary
Room Lifecycle Management
backend/controllers/debate_controller.go
Added CleanupInterval and RoomTTL constants. Implemented background cleanupRoutine via init() function that periodically invokes cleanupOldRooms() to remove debate rooms exceeding 24-hour TTL based on LastActivity timestamp. Logs deletion events.
Data Structure Enhancements
backend/controllers/debate_controller.go
Expanded DebateMessage struct with Phase, Message, and Timestamp fields. Extended DebateRoom struct with LastActivity field (time.Time) to track room liveness for cleanup decisions.
Activity Tracking
backend/controllers/debate_controller.go
Added LastActivity initialization on room creation and updates on message submission and transcript retrieval. Ensures accurate tracking of room usage for cleanup evaluation.
Dependencies
backend/controllers/debate_controller.go
Added log and time package imports to support background cleanup routine and timestamp operations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰✨ Old rooms fade like morning dew,
A cleanup routine makes all anew,
With LastActivity keeping score,
No debates linger evermore—
The TTL timer hops along! 🕐

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Beyond the cleanup routine, the PR expands DebateMessage and DebateRoom structs with Phase, Message, Timestamp, and LastActivity fields that exceed the scope of issue #303's memory management objective. Review whether DebateMessage/DebateRoom struct expansions are necessary for the cleanup feature or should be addressed in a separate PR to keep scope focused.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly matches the main change: implementing a cleanupRoutine to automatically delete inactive debate rooms after 24 hours, which is the primary objective of this PR.
Linked Issues check ✅ Passed The PR implements a cleanup routine with periodic checks (every 1 hour) to remove rooms inactive for 24 hours, directly addressing issue #303's request for a cleanup mechanism to prevent unbounded memory growth.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
backend/controllers/debate_controller.go (3)

146-157: ⚠️ Potential issue | 🔴 Critical

Critical: Path traversal vulnerability — roomID is unsanitised user input.

room.RoomID originates from the room query parameter and is interpolated directly into a filesystem path. An attacker can submit room=../../etc/cron.d/evil to write arbitrary files anywhere the process has permission.

The comment on line 153 acknowledges this but does not mitigate it. Sanitise or reject invalid IDs before they enter the map.

🔒 Proposed fix — validate roomID at the handler entry point

Add a validation helper and call it early in both handlers:

import "regexp"

var validRoomID = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)

Then in SubmitDebateMessageHandler (and similarly in GetDebateTranscriptHandler):

 	roomID := c.Query("room")
 	if roomID == "" {
 		c.JSON(http.StatusBadRequest, gin.H{"error": "room parameter required"})
 		return
 	}
+	if !validRoomID.MatchString(roomID) {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid room ID"})
+		return
+	}

149-156: ⚠️ Potential issue | 🟠 Major

Errors are silently swallowed.

Both the json.MarshalIndent error (line 150) and the os.WriteFile error (line 155 — empty if body) are discarded. In production you won't know when persistence is failing.

At minimum, log the errors:

♻️ Proposed fix
 	data, err := json.MarshalIndent(room, "", "  ")
 	if err != nil {
+		log.Printf("[Persist] Failed to marshal room %s: %v", room.RoomID, err)
 		return
 	}
-	// Note: Ensure roomID is sanitized in production to prevent path traversal
 	filename := fmt.Sprintf("room_%s.json", room.RoomID)
-	if err := os.WriteFile(filename, data, 0644); err != nil {
+	if err := os.WriteFile(filename, data, 0644); err != nil {
+		log.Printf("[Persist] Failed to write %s: %v", filename, err)
 	}

130-143: ⚠️ Potential issue | 🟠 Major

Data race: room is serialized without holding room.Mutex.

After debateRoomsMutex.Unlock() at line 137, c.JSON(http.StatusOK, room) at line 143 reads room.Messages without any lock. A concurrent SubmitDebateMessageHandler can be appending to Messages under room.Mutex (lines 113-115) at the same time, producing a data race on the slice header.

Lock room.Mutex around the serialization:

♻️ Proposed fix
 	if !exists {
 		c.JSON(http.StatusNotFound, gin.H{"error": "room not found"})
 		return
 	}
+	room.Mutex.Lock()
+	defer room.Mutex.Unlock()
 	c.JSON(http.StatusOK, room)
🤖 Fix all issues with AI agents
In `@backend/controllers/debate_controller.go`:
- Around line 57-79: In cleanupOldRooms, when deleting entries from the
debateRooms map, also delete the persisted JSON file(s) written by
persistDebateRoom (e.g., "room_<id>.json") by calling os.Remove for that
filename and handle/log any removal error; alternatively, check a persisted flag
on room (if you track persistence) and only remove files for rooms that were
persisted; update cleanupOldRooms (and ensure debateRoomsMutex remains held
while removing map entries) to perform the file deletion and emit a log on
failure/success.
🧹 Nitpick comments (4)
backend/controllers/debate_controller.go (4)

15-19: Consider making these constants unexported.

These constants are internal configuration for the cleanup routine and don't need to be part of the package's public API. Use lowercase names (cleanupInterval, roomTTL) to keep the API surface small.


42-55: Cleanup goroutine has no shutdown mechanism and hurts testability.

Starting a perpetual goroutine in init() means:

  1. It cannot be stopped on server shutdown (goroutine leak, potential writes after shutdown).
  2. It runs on every package import, including in tests, which is surprising and uncontrollable.

Consider accepting a context.Context and using ticker + ctx.Done() select, started explicitly from main rather than init().

♻️ Suggested approach
-func init() {
-	go cleanupRoutine()
-}
-
-func cleanupRoutine() {
-	ticker := time.NewTicker(CleanupInterval)
-	defer ticker.Stop()
-
-	for range ticker.C {
-		cleanupOldRooms()
-	}
-}
+func StartCleanupRoutine(ctx context.Context) {
+	go func() {
+		ticker := time.NewTicker(cleanupInterval)
+		defer ticker.Stop()
+		for {
+			select {
+			case <-ticker.C:
+				cleanupOldRooms()
+			case <-ctx.Done():
+				log.Println("[Memory Cleanup] Cleanup routine stopped.")
+				return
+			}
+		}
+	}()
+}

Then in main():

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
controllers.StartCleanupRoutine(ctx)

98-110: Redundant time.Now() on room creation.

Line 105 initialises LastActivity for new rooms, and line 109 unconditionally overwrites it. Remove the initialisation at line 105 or guard line 109 with if exists.

♻️ Minor cleanup
 	room, exists := debateRooms[roomID]
 	if !exists {
 		room = &DebateRoom{
-			RoomID:       roomID,
-			Messages:     []DebateMessage{},
-			LastActivity: time.Now(), // Initialize timestamp
+			RoomID:   roomID,
+			Messages: []DebateMessage{},
 		}
 		debateRooms[roomID] = room
 	}
 	room.LastActivity = time.Now() // Update timestamp on new message

30-35: Mutex field should be unexported.

Exporting sync.Mutex as a struct field (Mutex) exposes synchronization internals to callers. Use a lowercase name (mu is idiomatic Go) to keep it package-private.

Comment thread backend/controllers/debate_controller.go
aniket866 and others added 2 commits February 7, 2026 22:57
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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.

Unbounded In-Memory State Accumulation leads to Performance: Memory Leak in Chat Storage

1 participant