Rapptz / discord.py

An API wrapper for Discord written in Python.
http://discordpy.rtfd.org/en/latest
MIT License
14.73k stars 3.74k forks source link

aiohttp.client_exceptions.ServerDisconnectedError: None while sending message #842

Closed overfl0 closed 6 years ago

overfl0 commented 6 years ago

Branch: rewrite pip freeze yields: discord.py==1.0.0a1384

Stacktrace:

Traceback (most recent call last):
  File "/home/frontline/c/discordbots/discord_base.py", line 55, in periodic
    await function()
  File "/home/frontline/c/discordbots/commands.py", line 79, in periodic_check_server
    await self.send_players_notifications(players)
  File "/home/frontline/c/discordbots/commands.py", line 34, in send_players_notifications
    tmp = await self.get_channel(<CHANNEL_ID_HERE>).send(<SOME_MESSAGE_HERE>)
  File "/home/frontline/c/DEV3/lib/python3.5/site-packages/discord/abc.py", line 764, in send
    data = yield from state.http.send_message(channel.id, content, tts=tts, embed=embed, nonce=nonce)
  File "/home/frontline/c/DEV3/lib/python3.5/site-packages/discord/http.py", line 152, in request
    r = yield from self._session.request(method, url, **kwargs)
  File "/home/frontline/c/DEV3/lib/python3.5/site-packages/aiohttp/helpers.py", line 97, in __iter__
    ret = yield from self._coro
  File "/home/frontline/c/DEV3/lib/python3.5/site-packages/aiohttp/client.py", line 241, in _request
    yield from resp.start(conn, read_until_eof)
  File "/home/frontline/c/DEV3/lib/python3.5/site-packages/aiohttp/client_reqrep.py", line 559, in start
    (message, payload) = yield from self._protocol.read()
  File "/home/frontline/c/DEV3/lib/python3.5/site-packages/aiohttp/streams.py", line 509, in read
    yield from self._waiter
  File "/home/frontline/python35/lib/python3.5/asyncio/futures.py", line 380, in __iter__
    yield self  # This tells Task to wait for completion.
  File "/home/frontline/python35/lib/python3.5/asyncio/tasks.py", line 304, in _wakeup
    future.result()
  File "/home/frontline/python35/lib/python3.5/asyncio/futures.py", line 293, in result
    raise self._exception
aiohttp.client_exceptions.ServerDisconnectedError: None

Around two minutes later, judging by the logs, I also got the following:

WARNING:discord.gateway:Shard ID None has stopped responding to the gateway. Closing and restarting.
INFO:websockets.protocol:Failing the WebSocket connection: 1011
INFO:discord.gateway:Websocket closed with 1006 (), attempting a reconnect.
INFO:discord.client:Got a request to RESUME the websocket.
INFO:discord.gateway:Created websocket connected to wss://gateway.discord.gg?encoding=json&v=6
INFO:discord.gateway:Shard ID None has sent the RESUME payload.
INFO:discord.gateway:Shard ID None has successfully RESUMED session <SESSION_ID_HERE> under trace gateway-prd-main-71d4, discord-sessions-prd-1-7.

The message was never re-sent afterwards. Wasn't discord.py supposed to catch this exception and retry sending the message?

Note: self points to a class inheriting from discord.Client.

Gorialis commented 6 years ago

The 1006 websocket code means you got disconnected for some reason. This doesn't sound very helpful, but that's about as much information as the 1006 code gives you.

Judging by this happening on the standard gateway and not the voice one, I'm going to guess that this is a 'Connection reset by peer' error. To quote this thread:

"Connection reset by peer" is the TCP/IP equivalent of slamming the phone back on the hook.
It's more polite than merely not replying, leaving one hanging, but it's not the FIN-ACK expected of the truly polite TCP/IP converseur.

In short, for some reason, your connection was lost, and this was your network adapter's way of ensuring you weren't pointlessly listening to a dead connection. You shouldn't expect this often if you have a stable internet provider, and it's not really too much to worry about, especially because the library reconnects for you when it next can.

overfl0 commented 6 years ago

Thank you for your answer. I understand what is a connection reset by peer type of error, but I was saying that I would expect discord.py to catch that error and then try to reconnect and re-send the same message after establishing the connection, without raising the exception at all.

Note that my message has been lost forever now (and the whole code that was supposed to run afterwards was not run either, obviously).

Does this mean that I am expected to enclose ALL my message sending calls with try/except statements?!? This doesn't seem really reasonable.

Rapptz commented 6 years ago

This idea seems nice on paper but to actually do it requires a bit more effort than you think.

First, every request you send acquires a lock. This means that there's a "queue" for every type of request that is waiting on that lock. This is for rate limit handling.

Second, in order to figure out when we're connected to the internet again we have to actually do a request at specific intervals. This means that we have to sleep an indeterminate amount of time to retry the request and see if it succeeds. If it fails then we continue the loop at a higher interval (typically using exponential backoff -- similar to the rewrite connect logic).

There is already a 5-time retry for requests if they return a 502, extending the logic to deal with internet connection errors is a bit more complicated and can end up leading to the lock being indefinitely held for longer than it should be and just spamming discord with requests.

In the official client when you send a message without internet it doesn't wait until your internet is back to send the message probably for similar reasons.