aboutsummaryrefslogtreecommitdiffstats
path: root/cogs
diff options
context:
space:
mode:
authorsalmonik <raisersoft@proton.me>2026-05-21 16:45:31 +0200
committersalmonik <raisersoft@proton.me>2026-05-21 16:45:31 +0200
commit35545e30303faa517a98ec18c660e5f6c063c912 (patch)
tree36f5cf41b1dd1cf34f377804f20b76a462fcd219 /cogs
parentmake the assistant simpler (diff)
downloadsenator-sperma-35545e30303faa517a98ec18c660e5f6c063c912.tar.gz
senator-sperma-35545e30303faa517a98ec18c660e5f6c063c912.zip
Fix voice reconnect race: reuse VoiceClient instead of destroying it
The stale-connection path was calling _remove_voice_client() then creating a brand-new VoiceClient via channel.connect(). This created a race: VOICE_SERVER_UPDATE (triggered by Discord when the user joins voice) arrives between the destroy and the fresh-connect, gets consumed by the old client, and the new client never receives it — causing a 10s timeout waiting for got_both_voice_updates. Fix: after disconnect(), call vc.connect() on the same VoiceClient instance. The disconnect resets the state machine to 'disconnected' but the same VoiceConnectionState processes any events that arrive between disconnect and reconnect, so the state machine correctly reaches got_both_voice_updates.
Diffstat (limited to '')
-rw-r--r--cogs/voice.py54
1 files changed, 43 insertions, 11 deletions
diff --git a/cogs/voice.py b/cogs/voice.py
index f079c6a..a224336 100644
--- a/cogs/voice.py
+++ b/cogs/voice.py
@@ -80,17 +80,29 @@ class VoiceManager:
80 logger.info(f"Already connected to {channel}") 80 logger.info(f"Already connected to {channel}")
81 return vc 81 return vc
82 logger.warning(f"Stale connection to {channel}, reconnecting") 82 logger.warning(f"Stale connection to {channel}, reconnecting")
83 # Disconnect resets the state machine to 'disconnected'
84 # but keeps the VoiceClient alive. Reusing it avoids a
85 # race where Discord sends VOICE_SERVER_UPDATE between
86 # the destroy and the fresh-connect below, causing the
87 # new client to timeout waiting for the event (since the
88 # old one already consumed it).
83 try: 89 try:
84 await vc.disconnect() 90 await vc.disconnect()
85 except Exception: 91 except Exception:
86 pass 92 pass
87 # Forcefully clear stale internal state so the fresh
88 # connect doesn't hang for 30s waiting for Discord.
89 try: 93 try:
90 channel._state._remove_voice_client(guild.id) 94 await vc.connect(
91 except Exception: 95 reconnect=False, timeout=10.0, self_deaf=False, self_mute=False
92 pass 96 )
93 vc = None 97 logger.info(f"Reconnected to voice channel: {channel}")
98 return vc
99 except Exception as e:
100 logger.error(f"Failed to reconnect stale voice client: {e}")
101 try:
102 channel._state._remove_voice_client(guild.id)
103 except Exception:
104 pass
105 vc = None
94 else: 106 else:
95 try: 107 try:
96 await vc.move_to(channel) 108 await vc.move_to(channel)
@@ -102,18 +114,38 @@ class VoiceManager:
102 await vc.disconnect() 114 await vc.disconnect()
103 except Exception: 115 except Exception:
104 pass 116 pass
117 try:
118 await vc.connect(
119 reconnect=False, timeout=10.0, self_deaf=False, self_mute=False
120 )
121 logger.info(f"Reconnected after failed move: {channel}")
122 return vc
123 except Exception:
124 pass
125 try:
126 channel._state._remove_voice_client(guild.id)
127 except Exception:
128 pass
105 vc = None 129 vc = None
106 else: 130 else:
107 logger.info("Voice client exists but disconnected, cleaning up stale entry") 131 logger.info("Voice client exists but disconnected, reconnecting")
108 try: 132 try:
109 await vc.disconnect() 133 await vc.disconnect()
110 except Exception: 134 except Exception:
111 pass 135 pass
112 try: 136 try:
113 channel._state._remove_voice_client(guild.id) 137 await vc.connect(
114 except Exception: 138 reconnect=False, timeout=10.0, self_deaf=False, self_mute=False
115 pass 139 )
116 vc = None 140 logger.info(f"Reconnected disconnected client: {channel}")
141 return vc
142 except Exception as e:
143 logger.error(f"Failed to reconnect disconnected client: {e}")
144 try:
145 channel._state._remove_voice_client(guild.id)
146 except Exception:
147 pass
148 vc = None
117 149
118 # --- create fresh connection -------------------------------------- 150 # --- create fresh connection --------------------------------------
119 self._connecting.add(guild_id) 151 self._connecting.add(guild_id)