处理 asyncio 死锁问题

6

这段示例代码无限期挂起:

import asyncio


async def main():
    async def f():
        await g_task

    async def g():
        await f_task

    f_task = asyncio.create_task(f())
    g_task = asyncio.create_task(g())
    await f_task


asyncio.run(main())

我正在寻找一种自动检测和处理死锁的方法,就像GoLang所做的那样。
到目前为止,我想出了一种asyncio.wait_for()的变体:
[编辑]设计进行了大修 https://gist.github.com/gimperiale/549cbad04c24d870145d3f38fbb8e6f0 在原始代码中进行了1行更改:
await wait_check_deadlock(f_task)

它可以运行,但存在两个主要问题:

  1. 它依赖于 CPython 的实现细节 asyncio.Task._fut_waiter
  2. 死锁的任务将永远保留在 RAM 中。 aw.cancel() 似乎没有作用。如果我捕获了辅助函数引发的 RecursionError,则 asyncio.run() 在尝试取消所有任务时会引发另一个 RecursionError。

是否有更健壮的解决方案?


1
关于 Golang 的一点小提示——非常有可能死锁一个 Go 程序。我已经做过很多次了,特别是在试图覆盖大量使用通道的代码角落情况的单元测试中。 - Dima Tisnek
有一个问题,即Python的asyncioasync/await是否支持取消,因为只要对f_taskg_task的引用在“外部”可用,即对于某些其他协程,该其他代码可以调用f_task.cancel()。除了目前存在的问题:https://bugs.python.org/issue36456 - Dima Tisnek
顺便说一句,我认为你可以在 https://mail.python.org/mailman/listinfo/async-sig 上发布你的问题。 - Dima Tisnek
1个回答

3

避免死锁问题已经得到了很多研究,一些实用的解决方案已经存在,但在一般情况下,该问题是不可判定的(我认为可以将其归结为停机问题)。

为了说明实用性,考虑以下内容:

await asyncio.sleep(2 ** (1 / random.random()))

根据您的运气,它可能很快就会返回或者 "几乎永远"。

这个技巧可以用来展示基于回调的程序是不可预测的:

f = asyncio.Future()

async foo():
    await asyncio.sleep(2 ** (1 / random.random()))
    f.set_result(None)

async bar():
    await f

await asyncio.gather(foo(), bar())

同样,它也可以应用于您的“纯”异步/等待程序:
async def f():
    await g_task

async def g():
    await asyncio.wait(f_task,
                       asyncio.sleep(2 ** (1 / random.random())),
                       return_when=asyncio.FIRST_COMPLETED)

f_task = asyncio.create_task(f())
g_task = asyncio.create_task(g())
await f_task

同时,虽然不完美但实用的死锁检测器可以非常有用,请考虑将您的代码发布到核心asyncio开发者和/或独立库。

当前的惯例是使用PYTHONASYNCIODEBUG = 1运行测试,它会显示未等待的任务(在结果/异常被读取之前被销毁)。

您的库可以更好,例如,它可以报告某些任务花费的时间超过X,或当一个依赖于给定任务的任务DAG变得太大时。


2
我不认为过长的睡眠是死锁,就像没有客户端连接且服务器只是在等待select()时,我也不认为web/RPC服务器被卡住了。死锁特指任务之间的循环依赖关系。 - crusaderky
你的库可以更好,例如,它可以报告某些任务花费的时间超过了X,我认为没有必要重新实现 "wait_for(aw,timeout = ...)"。 - crusaderky
@PedrovonHertwigBatista,您是否想提供更好的答案? - Dima Tisnek
@DimaTisnek,我很想能够这样做,但不幸的是我也是因为搜索而遇到了这个问题。只是想为其他人提供更多的清晰度,如果我的语气不礼貌,对不起。 - Pedro von Hertwig Batista
1
作为一个更具可操作性的建议,我有一种感觉,库Hypothesis中存在的有状态测试可能对于检测可能死锁的异步代码很有用。这并不是理论上的保证(正如你所提到的棘手的停机问题),但至少可以捕捉一些错误。不幸的是,它目前似乎不支持异步代码,但我正在研究如何实现它。 - Pedro von Hertwig Batista
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接