异步上下文管理器是否需要保护它们的清理代码不被取消?

问题(我认为)

contextlib.asynccontextmanager文档给出了这样的例子:

@asynccontextmanager
async def get_connection():
    conn = await acquire_db_connection()
    try:
        yield conn
    finally:
        await release_db_connection(conn)

在我看来,这可能会泄漏资源。如果此代码的任务在此代码在其行上时被取消await release_db_connection(conn),则发布可能会中断。在asyncio.CancelledError将从某处内向上传播finally块,从而防止随后的清理代码运行。

因此,实际上,如果您正在实现一个处理超时请求的 Web 服务器,则在完全错误的时间触发超时可能会导致数据库连接泄漏。

完整的可运行示例

import asyncio
from contextlib import asynccontextmanager

async def acquire_db_connection():
    await asyncio.sleep(1)
    print("Acquired database connection.")
    return "<fake connection object>"

async def release_db_connection(conn):
    await asyncio.sleep(1)
    print("Released database connection.")

@asynccontextmanager
async def get_connection():
    conn = await acquire_db_connection()
    try:
        yield conn
    finally:
        await release_db_connection(conn)

async def do_stuff_with_connection():
    async with get_connection() as conn:
        await asyncio.sleep(1)
        print("Did stuff with connection.")

async def main():
    task = asyncio.create_task(do_stuff_with_connection())

    # Cancel the task just as the context manager running
    # inside of it is executing its cleanup code.
    await asyncio.sleep(2.5)
    task.cancel()
    try:
        await task
    except asyncio.CancelledError:
        pass

    print("Done.")

asyncio.run(main())

Python 3.7.9 上的输出:

Acquired database connection.
Did stuff with connection.
Done.

请注意,Released database connection永远不会打印。

我的问题

  • 一个问题,对吧?对我来说,直觉上,我期望.cancel()的意思是“优雅地取消,清理沿途使用的任何资源”。(否则,他们为什么要将取消实现为异常传播?)但我可能是错的。例如,也许.cancel()是为了快速而不是优雅。是否有权威来源澄清.cancel()在这里应该做什么?
  • 如果这确实是一个问题,我该如何解决?
以上是异步上下文管理器是否需要保护它们的清理代码不被取消?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>