HIGH: Voice Session Leaks - Unclosed Discord Audio Connections #4

Closed
opened 2026-02-16 22:00:47 +02:00 by Koko210 · 1 comment
Owner

What the Problem Is

When a user joins a voice channel and the bot creates a voice connection, the connection is not properly cleaned up if the bot crashes, is stopped forcefully, or encounters an error. Each leaked voice connection holds a WebSocket connection to Discord and can accumulate.

Where It Occurs

  • bot/voice_manager.py#L120-L125 - Voice session tracking
  • bot/voice_manager.py#L200-L210 - Connection lifecycle
  • bot/bot.py#L300 - asyncio.create_task(join_voice_channel()) without cleanup handler
  • bot/bot.py#L454 - asyncio.create_task(leave_voice_channel()) without cleanup handler

Why This Is a Problem

  1. Resource Leak: Each voice connection holds a WebSocket and audio streams
  2. Socket Exhaustion: Discord API limits concurrent WebSocket connections per bot
  3. Ghost Sessions: Users see bot as "connected" but bot does not respond
  4. Rate Limits: Repeated connection attempts may hit rate limits

What Can Go Wrong

Scenario 1: Bot Crashes During Voice Session

  1. Bot connects to voice channel #general, starts streaming audio
  2. Unhandled exception occurs in bot code (e.g., out of memory)
  3. Bot process terminates without cleanup
  4. Discord API still shows bot as "connected" to #general
  5. User tries to get bot to join voice channel again
  6. Bot creates new voice connection, but old one still exists
  7. After 10 crashes, bot has 10 ghost voice connections
  8. Discord API rejects new connections: "Already connected to voice"
  9. Bot cannot join voice channels until Discord times out ghost sessions (5-10 minutes)

Scenario 2: User Joins Multiple Channels

  1. User sends "!join" in channel #general
  2. Bot joins #general successfully
  3. User sends "!join" in channel #music (different channel)
  4. Bot joins #music, but #general connection never closed
  5. Bot now has 2 voice connections
  6. User sends "!join" in channel #offtopic
  7. Bot has 3 voice connections
  8. Audio plays in all 3 channels simultaneously (confusing)
  9. Discord API rate limits new voice connections
  10. Bot becomes unable to join new channels

Proposed Fix

1. Implement Voice Session Tracking and Cleanup

# bot/utils/voice_manager.py
class VoiceManager:
    def __init__(self):
        self.active_sessions = {}  # guild_id -> voice_client
        self.session_cleanup_tasks = {}  # guild_id -> asyncio.Task
    
    async def join_voice_channel(self, voice_channel):
        """Join a voice channel with automatic cleanup on disconnect"""
        guild_id = voice_channel.guild.id
        
        # Clean up any existing session
        if guild_id in self.active_sessions:
            await self.leave_voice_channel(guild_id)
        
        voice_client = await voice_channel.connect()
        self.active_sessions[guild_id] = voice_client
        
        # Set up disconnect handler
        @voice_client.event
        async def on_disconnect():
            logger.warning(f"Voice client disconnected from guild {guild_id}")
            self.active_sessions.pop(guild_id, None)
            self.session_cleanup_tasks.pop(guild_id, None)
        
        return voice_client
    
    async def leave_voice_channel(self, guild_id):
        """Leave voice channel and clean up resources"""
        if guild_id in self.active_sessions:
            voice_client = self.active_sessions[guild_id]
            await voice_client.disconnect()
            self.active_sessions.pop(guild_id, None)
        
        # Cancel any pending cleanup task
        if guild_id in self.session_cleanup_tasks:
            self.session_cleanup_tasks[guild_id].cancel()
            self.session_cleanup_tasks.pop(guild_id, None)
    
    async def cleanup_all_sessions(self):
        """Emergency cleanup for all voice sessions (called on bot shutdown)"""
        logger.info(f"Cleaning up {len(self.active_sessions)} voice sessions")
        for guild_id in list(self.active_sessions.keys()):
            await self.leave_voice_channel(guild_id)

2. Register Cleanup on Bot Shutdown

# bot/bot.py
from bot.utils.voice_manager import VoiceManager

voice_manager = VoiceManager()

async def cleanup_on_shutdown():
    """Cleanup all voice sessions before shutdown"""
    await voice_manager.cleanup_all_sessions()

# Register cleanup handler
bot.loop.create_task(cleanup_on_shutdown())

Severity

HIGH - Voice session leaks prevent bot from connecting to voice channels after crashes; ghost connections confuse users.

Files Affected

bot/utils/voice_manager.py, bot/bot.py, bot/command_router.py

## What the Problem Is When a user joins a voice channel and the bot creates a voice connection, the connection is not properly cleaned up if the bot crashes, is stopped forcefully, or encounters an error. Each leaked voice connection holds a WebSocket connection to Discord and can accumulate. ## Where It Occurs - `bot/voice_manager.py#L120-L125` - Voice session tracking - `bot/voice_manager.py#L200-L210` - Connection lifecycle - `bot/bot.py#L300` - `asyncio.create_task(join_voice_channel())` without cleanup handler - `bot/bot.py#L454` - `asyncio.create_task(leave_voice_channel())` without cleanup handler ## Why This Is a Problem 1. **Resource Leak**: Each voice connection holds a WebSocket and audio streams 2. **Socket Exhaustion**: Discord API limits concurrent WebSocket connections per bot 3. **Ghost Sessions**: Users see bot as "connected" but bot does not respond 4. **Rate Limits**: Repeated connection attempts may hit rate limits ## What Can Go Wrong ### Scenario 1: Bot Crashes During Voice Session 1. Bot connects to voice channel #general, starts streaming audio 2. Unhandled exception occurs in bot code (e.g., out of memory) 3. Bot process terminates without cleanup 4. Discord API still shows bot as "connected" to #general 5. User tries to get bot to join voice channel again 6. Bot creates new voice connection, but old one still exists 7. After 10 crashes, bot has 10 ghost voice connections 8. Discord API rejects new connections: "Already connected to voice" 9. **Bot cannot join voice channels until Discord times out ghost sessions (5-10 minutes)** ### Scenario 2: User Joins Multiple Channels 1. User sends "!join" in channel #general 2. Bot joins #general successfully 3. User sends "!join" in channel #music (different channel) 3. Bot joins #music, but #general connection never closed 4. Bot now has 2 voice connections 5. User sends "!join" in channel #offtopic 6. Bot has 3 voice connections 7. Audio plays in all 3 channels simultaneously (confusing) 8. Discord API rate limits new voice connections 9. **Bot becomes unable to join new channels** ## Proposed Fix ### 1. Implement Voice Session Tracking and Cleanup ```python # bot/utils/voice_manager.py class VoiceManager: def __init__(self): self.active_sessions = {} # guild_id -> voice_client self.session_cleanup_tasks = {} # guild_id -> asyncio.Task async def join_voice_channel(self, voice_channel): """Join a voice channel with automatic cleanup on disconnect""" guild_id = voice_channel.guild.id # Clean up any existing session if guild_id in self.active_sessions: await self.leave_voice_channel(guild_id) voice_client = await voice_channel.connect() self.active_sessions[guild_id] = voice_client # Set up disconnect handler @voice_client.event async def on_disconnect(): logger.warning(f"Voice client disconnected from guild {guild_id}") self.active_sessions.pop(guild_id, None) self.session_cleanup_tasks.pop(guild_id, None) return voice_client async def leave_voice_channel(self, guild_id): """Leave voice channel and clean up resources""" if guild_id in self.active_sessions: voice_client = self.active_sessions[guild_id] await voice_client.disconnect() self.active_sessions.pop(guild_id, None) # Cancel any pending cleanup task if guild_id in self.session_cleanup_tasks: self.session_cleanup_tasks[guild_id].cancel() self.session_cleanup_tasks.pop(guild_id, None) async def cleanup_all_sessions(self): """Emergency cleanup for all voice sessions (called on bot shutdown)""" logger.info(f"Cleaning up {len(self.active_sessions)} voice sessions") for guild_id in list(self.active_sessions.keys()): await self.leave_voice_channel(guild_id) ``` ### 2. Register Cleanup on Bot Shutdown ```python # bot/bot.py from bot.utils.voice_manager import VoiceManager voice_manager = VoiceManager() async def cleanup_on_shutdown(): """Cleanup all voice sessions before shutdown""" await voice_manager.cleanup_all_sessions() # Register cleanup handler bot.loop.create_task(cleanup_on_shutdown()) ``` ## Severity **HIGH** - Voice session leaks prevent bot from connecting to voice channels after crashes; ghost connections confuse users. ## Files Affected bot/utils/voice_manager.py, bot/bot.py, bot/command_router.py
Koko210 reopened this issue 2026-02-16 22:17:02 +02:00
Author
Owner

Addressed in commit 8d51370 as part of the graceful shutdown implementation. The new graceful_shutdown() function explicitly calls VoiceSessionManager.end_session() as its first step, which properly disconnects from voice, releases GPU locks, cleans up audio resources, and stops STT processing. This prevents ghost voice connections after bot restart.

Addressed in commit 8d51370 as part of the graceful shutdown implementation. The new graceful_shutdown() function explicitly calls VoiceSessionManager.end_session() as its first step, which properly disconnects from voice, releases GPU locks, cleans up audio resources, and stops STT processing. This prevents ghost voice connections after bot restart.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Koko210/miku-discord#4