如何在异步代码异常时启动 pdb

gel*_*ida 5 python pdb python-asyncio

我有一个 asyncio 应用程序,其中发生了一些异常。

让我们以这个小脚本为例:

import asyncio

async def coro1():
    for a in range(1, -1, -1):
       await asyncio.sleep(1)
       print("coro1", a)
       c = 1/a  # ### !!!Will raise exception when a is 0!!!!

async def coro2():
    for n in range(6):
        print("------- coro2", n)
        await asyncio.sleep(0.5)

async def main():
    coros = [coro1(), coro2()]
    completed, pending = await asyncio.wait(coros)
    print("comp", completed)
    print("pend", pending)

event_loop = asyncio.get_event_loop()
event_loop.run_until_complete(main())
Run Code Online (Sandbox Code Playgroud)

如果我对单线程非异步程序遇到类似的问题,我就会这样做

python -m pydb scriptname
Run Code Online (Sandbox Code Playgroud)

输入c并在代码中以 pydb 结束,这引发了异常,现在我可以查看和显示变量,我可以看到例如,那a是 0

我在我的 asyncio 应用程序中尝试了同样的操作,

我仅在 coro2 完成后收到异常,并且 pdb 在发生错误时不会停止。

我知道进入 pdb 会阻塞整个 asyncio 事件循环,但这只是为了事后分析,所以这不是问题。

如果输入 pdb 不起作用,那么我很好奇其他人如何调试此类问题。有没有更聪明的方法,然后在错误发生后添加打印和日志并希望错误再次发生?

这是我上面的脚本开始时的输出python -m pdb

(gelonida) gelonida@host:~/work/so/asynciopdb$ python -m pdb ./corodbg.py
[2] > /home/gelonida/work/so/asynciopdb/corodbg.py(2)<module>()
-> import asyncio
(Pdb++) c
---- coro2 0
---- coro2 1
coro1 1
---- coro2 2
---- coro2 3
coro1 0
---- coro2 4
---- coro2 5
comp {<Task finished coro=<coro2() done, defined at /home/gelonida/work/so/asynciopdb/corodbg.py:10> result=None>, <Task finished coro=<coro1() done, defined at /home/gelonida/work/so/asynciopdb/corodbg.py:4> exception=ZeroDivisionError('division by zero',)>}
pend set()
Task exception was never retrieved
future: <Task finished coro=<coro1() done, defined at /home/gelonida/work/so/asynciopdb/corodbg.py:4> exception=ZeroDivisionError('division by zero',)>
Traceback (most recent call last):
  File "/home/gelonida/work/so/asynciopdb/corodbg.py", line 8, in coro1
    c = 1/a
ZeroDivisionError: division by zero
The program finished and will be restarted
[2] > /home/gelonida/work/so/asynciopdb/corodbg.py(2)<module>()
-> import asyncio
(Pdb++)
Run Code Online (Sandbox Code Playgroud)

我安装了 pdbpp,但没有 pdbpp 也会发生同样的情况

附录: 我看到,我可以将以下参数之一添加到asyncio.wait()

return_when=FIRST_EXCEPTION

或者

return_when=FIRST_COMPLETED

如果发生异常,这将使我无法继续其他协程。另一方面,我仍然不知道如何进入 pdb 并访问失败的协程的后台跟踪。

附录 2: 我还发现我可以向 asyncio 添加一个异常处理程序,但这个处理程序似乎调用得太晚了。我做了以下操作:

def act_pdb(loop, context):
    print("EXC", loop, context)
    pdb.set_trace()
    loop.default_exception_handler(context)

event_loop.set_exception_handler(act_pdb)
Run Code Online (Sandbox Code Playgroud)

return_when=FIRST_COMPLETED pdb 结合将在异常发生后、其他协程完成之前被激活。但我不知道是否仍然可以将我移动到失败的coro的回溯或者coro是否已经被销毁。

我得到的回溯是:

[0]   /home/gelonida/work/so/asynciopdb/corodbg.py(34)<module>()
-> loop.run_until_complete(main())
[1]   /home/gelonida/.pyenv/versions/3.6.4/lib/python3.6/asyncio/base_events.py(454)run_until_complete()
-> self.run_forever()
[2]   /home/gelonida/.pyenv/versions/3.6.4/lib/python3.6/asyncio/base_events.py(421)run_forever()
-> self._run_once()
[3]   /home/gelonida/.pyenv/versions/3.6.4/lib/python3.6/asyncio/base_events.py(1431)_run_once()
-> handle._run()
[4]   /home/gelonida/.pyenv/versions/3.6.4/lib/python3.6/asyncio/events.py(145)_run()
-> self._callback(*self._args)
[5]   /home/gelonida/.pyenv/versions/3.6.4/lib/python3.6/asyncio/base_events.py(1299)call_exception_handler()
-> self._exception_handler(self, context)
[6] > /home/gelonida/work/so/asynciopdb/corodbg.py(30)bla()
-> loop.default_exception_handler(context)
Run Code Online (Sandbox Code Playgroud)