diff options
| author | salmonik <raisersoft@proton.me> | 2026-05-20 17:08:40 +0200 |
|---|---|---|
| committer | salmonik <raisersoft@proton.me> | 2026-05-20 17:08:40 +0200 |
| commit | 8d35930871db37b1009275c423ce95ed72954613 (patch) | |
| tree | 1372be685f4eb8764c316c06586b18d20732bc40 | |
| parent | Redirect play_commands imports to cogs.voice, remove audio_playback shim (diff) | |
| download | senator-sperma-8d35930871db37b1009275c423ce95ed72954613.tar.gz senator-sperma-8d35930871db37b1009275c423ce95ed72954613.zip | |
Fix stale-connection reconnect hang and TTS temp-file collision
- _ensure_voice_client: call _remove_voice_client() when cleaning up stale
or disconnected clients so the fresh connect doesn't hang for 30s
waiting for Discord's stale state to clear
- Reduce connect timeout from 30s to 10s for faster failure recovery
- vclogs.py: use a unique temp filename per TTS call (include timestamp)
instead of relying on PID alone, preventing concurrent TTS calls from
clobbering each other's files
| -rw-r--r-- | cogs/vclogs.py | 2 | ||||
| -rw-r--r-- | cogs/voice.py | 30 |
2 files changed, 27 insertions, 5 deletions
diff --git a/cogs/vclogs.py b/cogs/vclogs.py index 2e853ad..01a428d 100644 --- a/cogs/vclogs.py +++ b/cogs/vclogs.py | |||
| @@ -116,7 +116,7 @@ class VcLogs(commands.Cog): | |||
| 116 | 116 | ||
| 117 | voice_mgr = self.get_voice_manager() | 117 | voice_mgr = self.get_voice_manager() |
| 118 | temp_dir = get_temp_dir() | 118 | temp_dir = get_temp_dir() |
| 119 | tts_file = os.path.join(temp_dir, f"temp_tts_{os.getpid()}.mp3") | 119 | tts_file = os.path.join(temp_dir, f"temp_tts_{os.getpid()}_{asyncio.get_event_loop().time()}.mp3") |
| 120 | tts = gTTS(text=text, lang='pl') | 120 | tts = gTTS(text=text, lang='pl') |
| 121 | logger.info("saving file") | 121 | logger.info("saving file") |
| 122 | 122 | ||
diff --git a/cogs/voice.py b/cogs/voice.py index 86416ef..f079c6a 100644 --- a/cogs/voice.py +++ b/cogs/voice.py | |||
| @@ -84,6 +84,12 @@ class VoiceManager: | |||
| 84 | await vc.disconnect() | 84 | await vc.disconnect() |
| 85 | except Exception: | 85 | except Exception: |
| 86 | pass | 86 | pass |
| 87 | # Forcefully clear stale internal state so the fresh | ||
| 88 | # connect doesn't hang for 30s waiting for Discord. | ||
| 89 | try: | ||
| 90 | channel._state._remove_voice_client(guild.id) | ||
| 91 | except Exception: | ||
| 92 | pass | ||
| 87 | vc = None | 93 | vc = None |
| 88 | else: | 94 | else: |
| 89 | try: | 95 | try: |
| @@ -103,6 +109,10 @@ class VoiceManager: | |||
| 103 | await vc.disconnect() | 109 | await vc.disconnect() |
| 104 | except Exception: | 110 | except Exception: |
| 105 | pass | 111 | pass |
| 112 | try: | ||
| 113 | channel._state._remove_voice_client(guild.id) | ||
| 114 | except Exception: | ||
| 115 | pass | ||
| 106 | vc = None | 116 | vc = None |
| 107 | 117 | ||
| 108 | # --- create fresh connection -------------------------------------- | 118 | # --- create fresh connection -------------------------------------- |
| @@ -110,7 +120,7 @@ class VoiceManager: | |||
| 110 | try: | 120 | try: |
| 111 | await asyncio.sleep(0.3) | 121 | await asyncio.sleep(0.3) |
| 112 | try: | 122 | try: |
| 113 | vc = await channel.connect(timeout=30.0, reconnect=False) | 123 | vc = await channel.connect(timeout=10.0, reconnect=False) |
| 114 | logger.info(f"Connected to voice channel: {channel}") | 124 | logger.info(f"Connected to voice channel: {channel}") |
| 115 | return vc | 125 | return vc |
| 116 | except discord.ClientException as e: | 126 | except discord.ClientException as e: |
| @@ -129,7 +139,7 @@ class VoiceManager: | |||
| 129 | pass | 139 | pass |
| 130 | await asyncio.sleep(0.3) | 140 | await asyncio.sleep(0.3) |
| 131 | try: | 141 | try: |
| 132 | vc = await channel.connect(timeout=30.0, reconnect=False) | 142 | vc = await channel.connect(timeout=10.0, reconnect=False) |
| 133 | logger.info(f"Reconnected to voice channel: {channel}") | 143 | logger.info(f"Reconnected to voice channel: {channel}") |
| 134 | return vc | 144 | return vc |
| 135 | except Exception as e2: | 145 | except Exception as e2: |
| @@ -346,6 +356,9 @@ class VoiceManager: | |||
| 346 | async def play(self, ctx, source, delete_after_play=False, interrupt=True): | 356 | async def play(self, ctx, source, delete_after_play=False, interrupt=True): |
| 347 | """High-level playback: validate context, prepare source, connect, play, cleanup. | 357 | """High-level playback: validate context, prepare source, connect, play, cleanup. |
| 348 | 358 | ||
| 359 | Sends ``grom: {name}`` immediately on start, and an error follow-up | ||
| 360 | if playback fails. | ||
| 361 | |||
| 349 | Args: | 362 | Args: |
| 350 | ctx: Discord application context (used for channel resolution & error messages). | 363 | ctx: Discord application context (used for channel resolution & error messages). |
| 351 | source: Local file path or URL to stream. | 364 | source: Local file path or URL to stream. |
| @@ -377,14 +390,23 @@ class VoiceManager: | |||
| 377 | prepared_source, is_url, display_name | 390 | prepared_source, is_url, display_name |
| 378 | ) | 391 | ) |
| 379 | 392 | ||
| 393 | try: | ||
| 394 | await ctx.followup.send(f"grom: {final_display_name}") | ||
| 395 | except Exception as e: | ||
| 396 | logger.error(e) | ||
| 397 | |||
| 380 | success = await self._play_await(vc, audio_source) | 398 | success = await self._play_await(vc, audio_source) |
| 381 | 399 | ||
| 382 | if success: | 400 | if success: |
| 383 | logger.info(f"Playback completed: {final_display_name}") | 401 | logger.info(f"Playback completed: {final_display_name}") |
| 384 | else: | 402 | else: |
| 385 | logger.error(f"Playback failed: {prepared_source}") | 403 | logger.error(f"Playback failed: {prepared_source}") |
| 386 | 404 | try: | |
| 387 | if not success: | 405 | await ctx.followup.send( |
| 406 | "eror: Timeout - nie udało się połączyć ze streamem", ephemeral=True | ||
| 407 | ) | ||
| 408 | except Exception: | ||
| 409 | pass | ||
| 388 | await VoiceManager.cleanup_if_needed(prepared_source, delete_after_play, is_url) | 410 | await VoiceManager.cleanup_if_needed(prepared_source, delete_after_play, is_url) |
| 389 | 411 | ||
| 390 | return success | 412 | return success |
