Skip to content

Conversation

@yugalkaushik
Copy link

@yugalkaushik yugalkaushik commented Dec 26, 2025

Fixes #252
Added proper asyncio task cancellation pattern by:

  • Calling task.cancel() to schedule cancellation
  • Awaiting the task to ensure cleanup completes
  • Catching and suppressing the expected CancelledError

This ensures the Redis pubsub cleanup in chat_pubsub.py (unsubscribe() and close()) executes properly on every WebSocket disconnection.

✅ Checklist

  • I have read the contributing guidelines.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have added necessary documentation (if applicable).
  • Any dependent changes have been merged and published in downstream modules.

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced stability during chat disconnections by improving error handling in the shutdown process to prevent unhandled errors and ensure proper resource cleanup.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 26, 2025

📝 Walkthrough

Walkthrough

The WebSocket chat handler in Backend/app/routes/chat.py was modified to properly await the cancelled listener_task after cancellation occurs. Both the WebSocketDisconnect and generic Exception handlers now await the task and suppress asyncio.CancelledError, ensuring cleanup code executes before disconnection.

Changes

Cohort / File(s) Summary
WebSocket Task Cleanup
Backend/app/routes/chat.py
Added await listener_task wrapped in try-except to catch asyncio.CancelledError in both WebSocketDisconnect and Exception handlers. This ensures the cancelled task is properly awaited and cleanup code executes before disconnect, preventing resource leaks from unclosed Redis pubsub connections.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~5 minutes

Poem

🐰 A task was cancelled without a farewell,
Leaving cleanup code trapped in a shell,
Now we await it with grace and care,
And swallow the error floating through the air—
No more resource leaks, the connection is fair! 🔗

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: fixing a resource leak in WebSocket handler by properly awaiting cancelled asyncio tasks, which directly matches the code changes.
Linked Issues check ✅ Passed The PR implements all requirements from issue #252: cancels listener_task, awaits it, catches CancelledError, and ensures Redis cleanup executes.
Out of Scope Changes check ✅ Passed All changes are limited to proper asyncio task cancellation handling in WebSocket exception blocks, directly addressing the resource leak issue.
✨ 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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
Backend/app/routes/chat.py (1)

51-51: Improve error handling and logging.

Two suggestions for better error handling:

  1. Line 51: While catching broad Exception is flagged by Ruff (BLE001), it's acceptable here to ensure cleanup. However, consider whether to re-raise the exception after cleanup to propagate it appropriately.

  2. Line 59: Replace print() with proper logging using Python's logging module for production-ready error tracking.

🔎 Proposed improvements
+import logging
+
+logger = logging.getLogger(__name__)

Then update the exception handler:

     except Exception as e:
         listener_task.cancel()
         try:
             await listener_task
         except asyncio.CancelledError:
             pass
         await chat_service.disconnect(user_id, redis, db)
-        # Optionally log the error
-        print(f"Error in websocket for user {user_id}: {e}")
+        logger.error(f"Error in websocket for user {user_id}: {e}", exc_info=True)
+        # Optionally re-raise if you want the error to propagate
+        # raise

Also applies to: 59-59

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3be437 and ef6fdad.

📒 Files selected for processing (1)
  • Backend/app/routes/chat.py
🧰 Additional context used
🧬 Code graph analysis (1)
Backend/app/routes/chat.py (1)
Backend/app/services/chat_services.py (1)
  • disconnect (48-60)
🪛 Ruff (0.14.10)
Backend/app/routes/chat.py

51-51: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (1)
Backend/app/routes/chat.py (1)

43-49: Excellent fix for the resource leak!

The proper asyncio task cancellation pattern is correctly implemented:

  1. cancel() schedules the cancellation
  2. await listener_task ensures cleanup code executes (e.g., Redis pubsub unsubscribe/close in listen_to_channel)
  3. CancelledError is suppressed as expected

This ensures no resource leaks when WebSocket disconnects.

Comment on lines 51 to 57
except Exception as e:
listener_task.cancel()
try:
await listener_task
except asyncio.CancelledError:
pass
await chat_service.disconnect(user_id, redis, db)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's look at the file structure
find Backend -name "chat.py" -type f | head -20

Repository: AOSSIE-Org/InPactAI

Length of output: 116


🏁 Script executed:

#!/bin/bash
# Look at the specific lines mentioned (51-57) with context
if [ -f "Backend/app/routes/chat.py" ]; then
    echo "=== Lines 40-70 of Backend/app/routes/chat.py ==="
    sed -n '40,70p' Backend/app/routes/chat.py | cat -n
fi

Repository: AOSSIE-Org/InPactAI

Length of output: 1250


🏁 Script executed:

#!/bin/bash
# Search for listen_to_channel function
rg -n "listen_to_channel" Backend/app --type py -A 5 -B 2

Repository: AOSSIE-Org/InPactAI

Length of output: 1558


🏁 Script executed:

#!/bin/bash
# Get the full listen_to_channel function implementation
rg -n "async def listen_to_channel" Backend/app/services/chat_pubsub.py -A 30

Repository: AOSSIE-Org/InPactAI

Length of output: 530


🏁 Script executed:

#!/bin/bash
# Also check the full file to understand structure better
wc -l Backend/app/services/chat_pubsub.py

Repository: AOSSIE-Org/InPactAI

Length of output: 101


🏁 Script executed:

#!/bin/bash
# View the full Backend/app/services/chat_pubsub.py file if it's not too large
cat -n Backend/app/services/chat_pubsub.py

Repository: AOSSIE-Org/InPactAI

Length of output: 689


🏁 Script executed:

#!/bin/bash
# Check what chat_service.disconnect does
rg -n "def disconnect" Backend/app/services --type py -A 10

Repository: AOSSIE-Org/InPactAI

Length of output: 959


Task cancellation and pubsub cleanup are correct.

The exception handler properly cancels the listener task and awaits it with CancelledError suppression. The listen_to_channel function includes a finally block that unsubscribes and closes the Redis pubsub connection, so cleanup will execute when the task is cancelled.

However, two improvements are needed:

  1. Explicitly close the websocket after an exception: Currently, chat_service.disconnect() only removes the connection from tracking and updates user status—it does not explicitly close the websocket. After a generic exception, the websocket may be left in an inconsistent state. Consider calling await websocket.close() before cleanup.

  2. Replace print() with proper logging: Use a logger instead of print() for production-ready error reporting.

🧰 Tools
🪛 Ruff (0.14.10)

51-51: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
In Backend/app/routes/chat.py around lines 51 to 57, update the exception
handler to (1) explicitly close the websocket before doing service cleanup by
awaiting websocket.close() so the socket is left in a consistent state, and (2)
replace any print() calls with the module logger (e.g. logger.error(...)) and
include exception information (the exception object/traceback) in the log
message; then proceed to cancel/await the listener task and call
chat_service.disconnect(user_id, redis, db) as before.

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:Resource Leak in WebSocket Handler - Asyncio Task Not Properly Awaited After Cancellation

1 participant