Problem
My discord.py bot's client.run() is returning unexpectedly every few days or so followed by an error "Task was destroyed but it is pending!".
Question
Why is client.run() returning at all? And how can I change my bot to properly handle this issue and run forever?
Code (stripped out a lot to keep it simple for discussion):
import asyncio
import discord
TOKEN = 'my bot token goes here'
CHANNEL_ID = 'my channel id goes here'
async def DiscordMsgSendTask():
await client.wait_until_ready()
my_channel = discord.Object(id=CHANNEL_ID)
while not client.is_closed:
# wait a bit to prevent busy loop
await asyncio.sleep(2)
# check for and handle new event here
# if an event was handled then send a message to the channel
embed = discord.Embed(description='Event was handled')
await client.send_message(my_channel, embed=embed)
client = discord.Client()
while True:
client.loop.create_task(DiscordMsgSendTask())
try:
client.run(TOKEN)
except Exception as e:
logging.warning('Exception: ' + str(e))
client = discord.Client()
Additional Info
The bot's purpose is basically just to check for a new file in a specific directory, and then send a message to a specific discord channel to report it. This only occurs around 10 times a day, so the bot's traffic is extremely low.
In an attempt to make the bot tolerate errors/disconnects, I put it into the while True loop, which may be wrong approach.
Using Python 3.6.5 and Discord.py 0.16.12
Edit - Added traceback
Adding traceback from a previous failure earlier in the day.
2018-06-20 04:33:08 [ERROR] Task exception was never retrieved
future: <Task finished coro=<WebSocketCommonProtocol.run() done, defined at /usr/local/lib64/python3.6/site-packages/websockets/protocol.py:428> exception=ConnectionResetError(104, 'Connection reset by peer')>
Traceback (most recent call last):
File "/usr/local/lib64/python3.6/site-packages/websockets/protocol.py", line 434, in run
msg = yield from self.read_message()
File "/usr/local/lib64/python3.6/site-packages/websockets/protocol.py", line 456, in read_message
frame = yield from self.read_data_frame(max_size=self.max_size)
File "/usr/local/lib64/python3.6/site-packages/websockets/protocol.py", line 511, in read_data_frame
frame = yield from self.read_frame(max_size)
File "/usr/local/lib64/python3.6/site-packages/websockets/protocol.py", line 546, in read_frame
self.reader.readexactly, is_masked, max_size=max_size)
File "/usr/local/lib64/python3.6/site-packages/websockets/framing.py", line 86, in read_frame
data = yield from reader(2)
File "/usr/lib64/python3.6/asyncio/streams.py", line 674, in readexactly
yield from self._wait_for_data('readexactly')
File "/usr/lib64/python3.6/asyncio/streams.py", line 464, in _wait_for_data
yield from self._waiter
File "/usr/lib64/python3.6/asyncio/selector_events.py", line 723, in _read_ready
data = self._sock.recv(self.max_size)
ConnectionResetError: [Errno 104] Connection reset by peer
2018-06-20 04:33:08 [ERROR] Task was destroyed but it is pending!
task: <Task pending coro=<DiscordMsgSendTask() running at /home/bot.py:119> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7fc99bfd7a68>()]>>
2018-06-20 04:33:08 [ERROR] Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x7fc999b59240>
You can't use the pre-defined run
method since that only handles KeyboardInterrupt
. You will need to create a exit strategy that closes all tasks while still leaving the loop running.
The following example will raise SystemExit
(to simulate your unexpected fatal error (The RST - ConnectionError error)) when a message "die" is received by the bot. The SystemExit
will get "caught" and restarts your bot. Upon KeyboardInterrupt
is raised, the bot will exit successfully without error.
import asyncio
import discord
TOKEN = 'TOKEN_HERE'
client = discord.Client()
async def task():
await client.wait_until_ready()
while True:
await asyncio.sleep(1)
print('Running')
def handle_exit():
print("Handling")
client.loop.run_until_complete(client.logout())
# For python 3.9, use asyncio.all_tasks instead
for t in asyncio.Task.all_tasks(loop=client.loop):
if t.done():
t.exception()
continue
t.cancel()
try:
client.loop.run_until_complete(asyncio.wait_for(t, 5, loop=client.loop))
t.exception()
except (asyncio.InvalidStateError, asyncio.TimeoutError, asyncio.CancelledError):
pass
while True:
@client.event
async def on_message(m):
if m.content == 'die':
print("Terminating")
raise SystemExit
client.loop.create_task(task())
try:
client.loop.run_until_complete(client.start(TOKEN))
except SystemExit:
handle_exit()
except KeyboardInterrupt:
handle_exit()
client.loop.close()
print("Program ended")
break
print("Bot restarting")
client = discord.Client(loop=client.loop)
In Discord (someone types die
):
die
In terminal (STDOUT):
Running
Running
Running
Terminating
Handling
Bot restarting
Running
Running
Running
Running
Running
<CTRL-C> (KeyboardInterrupt)
Handling
Program ended
Side note:
If you're still confused about your error, it's not your code to blame. Errno 104 is a server side fatal error that's usually unpreventable by the end users.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With