关于python:如果client.run()返回,如何使discord.py bot永远运行

How to make discord.py bot run forever if client.run() returns

问题

我的discord.py bot的client.run()每隔几天就意外返回一次,随后出现一个错误"任务已被破坏,但仍处于挂起状态!".

问题

为什么client.run()会返回?我怎样才能改变我的机器人程序来正确处理这个问题并永远运行?

代码(为了便于讨论,去掉了很多代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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()

附加信息

bot的目的基本上只是检查特定目录中的新文件,然后将消息发送到特定的discord通道来报告它。这种情况一天只发生10次,所以机器人的流量非常低。

为了让bot能够容忍错误/断开连接,我将它放入while-true循环,这可能是错误的方法。

使用python 3.6.5和discord.py 0.16.12

编辑-添加的回溯

添加当天早些时候以前失败的回溯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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:


不能使用预定义的run方法,因为它只处理KeyboardInterrupt。您将需要创建一个退出策略,在保持循环运行的同时关闭所有任务。

以下示例将在bot收到消息"die"时引发SystemExit(以模拟意外的致命错误(rst-connectionerror错误))。SystemExit将被"捕获"并重新启动bot。提出KeyboardInterrupt后,bot将顺利退出,不会出错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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 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:
            pass
        except asyncio.TimeoutError:
            pass
        except 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)

不协调(某人键入die):

1
die

输入终端(stdout):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Running
Running
Running
Terminating
Handling
Bot restarting
Running
Running
Running
Running
Running
<CTRL-C> (KeyboardInterrupt)
Handling
Program ended

旁注:

如果你仍然对你的错误感到困惑,那不是你的错。errno 104是一个服务器端的致命错误,最终用户通常无法预防。