Browse Source

Ensure Client.close() has finished in __aexit__

This wraps the closing behavior in a task. Subsequent callers of
.close() now await that same close finishing rather than short
circuiting. This prevents a user-called close outside of __aexit__ from
not finishing before no longer having a running event loop.
pull/10109/head
Michael H 1 year ago
committed by dolfies
parent
commit
105c55a0b1
  1. 21
      discord/client.py

21
discord/client.py

@ -317,7 +317,7 @@ class Client:
self._enable_debug_events: bool = options.pop('enable_debug_events', False) self._enable_debug_events: bool = options.pop('enable_debug_events', False)
self._sync_presences: bool = options.pop('sync_presence', True) self._sync_presences: bool = options.pop('sync_presence', True)
self._connection: ConnectionState = self._get_state(**options) self._connection: ConnectionState = self._get_state(**options)
self._closed: bool = False self._closing_task: Optional[asyncio.Task[None]] = None
self._ready: asyncio.Event = MISSING self._ready: asyncio.Event = MISSING
if VoiceClient.warn_nacl: if VoiceClient.warn_nacl:
@ -334,7 +334,10 @@ class Client:
exc_value: Optional[BaseException], exc_value: Optional[BaseException],
traceback: Optional[TracebackType], traceback: Optional[TracebackType],
) -> None: ) -> None:
if not self.is_closed(): # This avoids double-calling a user-provided .close()
if self._closing_task:
await self._closing_task
else:
await self.close() await self.close()
# Internals # Internals
@ -969,11 +972,10 @@ class Client:
Closes the connection to Discord. Closes the connection to Discord.
""" """
if self._closed: if self._closing_task:
return return await self._closing_task
self._closed = True
async def _close():
for voice in self.voice_clients: for voice in self.voice_clients:
try: try:
await voice.disconnect(force=True) await voice.disconnect(force=True)
@ -991,6 +993,9 @@ class Client:
self.loop = MISSING self.loop = MISSING
self._closing_task = asyncio.create_task(_close())
await self._closing_task
def clear(self) -> None: def clear(self) -> None:
"""Clears the internal state of the bot. """Clears the internal state of the bot.
@ -998,7 +1003,7 @@ class Client:
and :meth:`is_ready` both return ``False`` along with the bot's internal and :meth:`is_ready` both return ``False`` along with the bot's internal
cache cleared. cache cleared.
""" """
self._closed = False self._closing_task = None
self._ready.clear() self._ready.clear()
self._connection.clear(full=True) self._connection.clear(full=True)
self.http.clear() self.http.clear()
@ -1114,7 +1119,7 @@ class Client:
def is_closed(self) -> bool: def is_closed(self) -> bool:
""":class:`bool`: Indicates if the websocket connection is closed.""" """:class:`bool`: Indicates if the websocket connection is closed."""
return self._closed return self._closing_task is not None
@property @property
def voice_client(self) -> Optional[VoiceProtocol]: def voice_client(self) -> Optional[VoiceProtocol]:

Loading…
Cancel
Save