Skip to content

refactor: Update Client.run to have a better async I/O usage #2645

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
de49cf5
fix: client.run not allowing bots to start
DA-344 Nov 11, 2024
4fe559f
chore: Update more things
DA-344 Nov 11, 2024
74863c5
chore: Move the loop update to .start
DA-344 Nov 11, 2024
72f904c
chore: Added logging and updated `run` to specify the arguments
DA-344 Nov 11, 2024
7e7f1e9
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 11, 2024
f289f74
chore: Updated CHANGELOG
DA-344 Nov 11, 2024
2195dc6
Merge changes from remote
DA-344 Nov 11, 2024
3d235ee
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 11, 2024
07ccbc9
dot
DA-344 Nov 11, 2024
ef835dc
Merge branch 'master' into fix/client-run
Lulalaby Nov 11, 2024
5e8a322
Update docstrings
DA-344 Nov 19, 2024
de5156c
Update docstrings
DA-344 Nov 19, 2024
b830a94
merge 'master' from https://github.com/Pycord-Development/pycord
DA-344 Nov 19, 2024
88484e2
chore: Update Client.__aenter__ and Client.run
DA-344 Dec 11, 2024
ba81ebe
feat: Add operations container to Client docstring
DA-344 Dec 11, 2024
f5f0085
chore: merge branch 'master' from Pycord-Development/pycord
DA-344 Dec 11, 2024
28fab35
chore: Update Client.close to prevent double closing and race conditions
DA-344 Dec 11, 2024
c378d41
fix: Indentation error
DA-344 Dec 11, 2024
e818a1c
Merge branch 'master' into fix/client-run
Dorukyum Jan 31, 2025
e963341
chore: Update Client.close and Client.clear to correctly update and u…
DA-344 Feb 1, 2025
e19e029
Merge branch 'master' into fix/client-run
DA-344 Mar 20, 2025
abf0ca0
Merge branch 'master' of https://github.com/Pycord-Development/pycord…
DA-344 Mar 23, 2025
81541a3
Merge branch 'master' into fix/client-run
DA-344 Apr 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ These changes are available on the `master` branch, but have not yet been releas
([#2627](https://github.com/Pycord-Development/pycord/issues/2627))
- Fixed `AttributeError` when sending polls with `PartialWebook`.
([#2624](https://github.com/Pycord-Development/pycord/pull/2624))
- Fixed Async I/O errors that could be raised when using `Client.run`.
([#2645](https://github.com/Pycord-Development/pycord/pull/2645))

### Changed

Expand Down
65 changes: 30 additions & 35 deletions discord/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@

import asyncio
import logging
import signal
import sys
import traceback
from types import TracebackType
Expand Down Expand Up @@ -221,14 +220,12 @@ class Client:
def __init__(
self,
*,
loop: asyncio.AbstractEventLoop | None = None,
loop: asyncio.AbstractEventLoop = MISSING,
**options: Any,
):
# self.ws is set in the connect method
self.ws: DiscordWebSocket = None # type: ignore
self.loop: asyncio.AbstractEventLoop = (
asyncio.get_event_loop() if loop is None else loop
)
self.loop: asyncio.AbstractEventLoop = loop
self._listeners: dict[str, list[tuple[asyncio.Future, Callable[..., bool]]]] = (
{}
)
Expand Down Expand Up @@ -751,10 +748,16 @@ async def start(self, token: str, *, reconnect: bool = True) -> None:
TypeError
An unexpected keyword argument was received.
"""
# Update the loop to get the running one in case the one set is MISSING
if self.loop is MISSING:
self.loop = asyncio.get_event_loop()
self.http.loop = self.loop
self._connection.loop = self.loop

await self.login(token)
await self.connect(reconnect=reconnect)

def run(self, *args: Any, **kwargs: Any) -> None:
def run(self, token: str, *, reconnect: bool = True) -> None:
"""A blocking call that abstracts away the event loop
initialisation from you.

Expand All @@ -765,54 +768,46 @@ def run(self, *args: Any, **kwargs: Any) -> None:
Roughly Equivalent to: ::

try:
loop.run_until_complete(start(*args, **kwargs))
asyncio.run(start(token))
except KeyboardInterrupt:
loop.run_until_complete(close())
# cancel all tasks lingering
finally:
loop.close()
return

Parameters
----------
token: :class:`str`
The authentication token. Do not prefix this token with anything as the library will do it for you.
reconnect: :class:`bool`
If we should attempt reconnecting to the gateway, either due to internet failure or a specific failure on Discord's part.
Certain disconnects that lead to bad state will not be handled (such as invalid sharding payloads or bad tokens).

.. warning::

This function must be the last function to call due to the fact that it
is blocking. That means that registration of events or anything being
called after this function call will not execute until it returns.
"""
loop = self.loop

try:
loop.add_signal_handler(signal.SIGINT, loop.stop)
loop.add_signal_handler(signal.SIGTERM, loop.stop)
except (NotImplementedError, RuntimeError):
pass

async def runner():
try:
await self.start(*args, **kwargs)
await self.start(token, reconnect=reconnect)
finally:
if not self.is_closed():
await self.close()

def stop_loop_on_completion(f):
loop.stop()
run = asyncio.run

if self.loop is not MISSING:
run = self.loop.run_until_complete

future = asyncio.ensure_future(runner(), loop=loop)
future.add_done_callback(stop_loop_on_completion)
try:
loop.run_forever()
except KeyboardInterrupt:
_log.info("Received signal to terminate bot and event loop.")
run(runner())
finally:
future.remove_done_callback(stop_loop_on_completion)
_log.info("Cleaning up tasks.")
_cleanup_loop(loop)
# Ensure the bot is closed
if not self.is_closed():
self.loop.run_until_complete(self.close())

if not future.cancelled():
try:
return future.result()
except KeyboardInterrupt:
# I am unsure why this gets raised here but suppress it anyway
return None
_log.info("Cleaning up tasks.")
_cleanup_loop(self.loop)

# properties

Expand Down