如果在异常之后不重置 ContextVar 是否会在异步逻辑中泄漏内存?

问题描述

如果我在异步网络服务器中有一个结构

import contextvars
...
my_context_var = contextvars.Contextvar("var")

@app.route("/foo")  # decorator from webserver
async def some_web_endpoint():
    local_ctx_var = my_context_var.set(params.get("bar"))  # app sets params
    await some_function_that_can_raise()
    local_ctx_var.reset()

如果我不将 Contextvar 包裹在 finally: 块中并且 some_function_that_can_raise() 引发异常,它会泄漏内存吗?
(没有这种情况,.reset() 永远不会被调用

    try:
        await some_function_that_can_raise()
    finally:
        local_ctx_var.reset()

.. 或者假设该值在请求范围结束时将被销毁是否安全?
The async example in the upstream docs doesn't actually bother .reset()-ing it at all
在这种情况下,.reset() 是多余的,因为它发生在上下文被清理之前。


为了添加更多上下文 (ha),我最近正在学习 Contextvars,我认为第二种情况是这样。

local_ctx_var 是唯一引用 Token (from .set()) 的名称,并且由于名称在请求范围结束时被删除,因此本地值应该成为垃圾收集的候选,防止潜在的泄漏并使 .reset() 对于短期作用域没有必要(万岁)

..但我不是绝对确定,虽然有一些关于这个主题的非常有用的信息,但它稍微混淆了混合物

解决方法

是 - 在这种情况下,context_var 的先前值保留在令牌对象中。有这个 rather similar question,其中一个答案运行一个简单的基准测试,以断言多次调用 context_var.set() 并丢弃返回值不会消耗内存,例如与创建新字符串和保持对它的引用。

鉴于基准,我做了一些进一步的实验并得出结论,没有泄漏 - 事实上,在上面的代码中,调用 reset 确实是多余的 - 如果您必须恢复以前的由于某种原因,循环结构中的值。

在最后保存的上下文的顶部设置了新的变量,在此过程中,上下文的当前版本中设置的值被简单地丢弃:对它的唯一引用是剩下的在令牌中,如果有的话。换句话说:以“类似堆栈”的方式保留先前值的是仅调用 contextvars.runcontextvars.copy_context,而不是 Contextvar.set