Skip to content

Incorrect Context in corotine's except and finally blocks #93740

@ProgramRipper

Description

@ProgramRipper

Bug report

Now we have the following code to show what happen:

import asyncio
from contextvars import ContextVar

ctx = ContextVar("test")
loop = asyncio.new_event_loop()

test.set("global")
print('expected to be "global":', ctx.get())


async def main():
    test.set("inner")
    print('expected to be "inner":', ctx.get())
    try:
        await asyncio.sleep(5) # may exit here
        raise Exception("this is the expected case")
    except BaseException as e:
        print('in except, expected to be "inner":', ctx.get())
        raise e
    finally:
        print('in finally, expected to be "inner":', ctx.get())


loop.run_until_complete(main())

If I left it run to the end, the result will as expected:

expected to be "global": global
expected to be "inner": inner
in except, expected to be "inner": inner
in finally, expected to be "inner": inner
Traceback (most recent call last):
  File "/home/programripper/PycharmProjects/test/test.py", line 25, in <module>
    loop.run_until_complete(main())
  File "/home/programripper/.pyenv/versions/3.10.1/lib/python3.10/asyncio/base_events.py", line 641, in run_until_complete
    return future.result()
  File "/home/programripper/PycharmProjects/test/test.py", line 20, in main
    raise e
  File "/home/programripper/PycharmProjects/test/test.py", line 17, in main
    raise Exception("this is the expected case")
Exception: this is the expected case

But if I interrupt it while running, it turns to:

expected to be "global": global
expected to be "inner": inner
^CTraceback (most recent call last):
  File "/home/programripper/PycharmProjects/test/test.py", line 22, in <module>
    loop.run_until_complete(main())
  File "/home/programripper/.pyenv/versions/3.10.1/lib/python3.10/asyncio/base_events.py", line 628, in run_until_complete
    self.run_forever()
  File "/home/programripper/.pyenv/versions/3.10.1/lib/python3.10/asyncio/base_events.py", line 595, in run_forever
    self._run_once()
  File "/home/programripper/.pyenv/versions/3.10.1/lib/python3.10/asyncio/base_events.py", line 1845, in _run_once
    event_list = self._selector.select(timeout)
  File "/home/programripper/.pyenv/versions/3.10.1/lib/python3.10/selectors.py", line 469, in select
    fd_event_list = self._selector.poll(timeout, max_ev)
KeyboardInterrupt
in except, expected to be "inner": global
in finally, expected to be "inner": global

Obviously, the ctx.get() in except and finally blocks didn't work as expected.

What's more, this not only happen by KeyboardInterrupt, but also other operations that will triger except or finally block, such as garbage collect.

As it is hard to trigger gc, so I can't give a minimal case, but a real case https://github.com/GraiaProject/BroadcastControl/blob/6a4a13e3531109bcb82dd4b306e7498d2bff9b0b/src/graia/broadcast/__init__.py#L207:

2022-06-12 14:55:14.445 | ERROR    | graia.ariadne.util:loguru_exc_callback_async:103 - Exception: {'message': 'Task was destroyed but it is pending!', 'task': <Task pending name='Task-112' coro=<Broadcast.layered_scheduler() done, defined at /home/programripper/PycharmProjects/test/__pypackages__/3.10/lib/graia/broadcast/__init__.py:97> wait_for=<Future pending cb=[Task.task_wakeup()]>>}
Exception ignored in: <coroutine object Broadcast.Executor at 0x7fdf404553f0>
Traceback (most recent call last):
  File "/home/programripper/PycharmProjects/test/__pypackages__/3.10/lib/graia/broadcast/__init__.py", line 207, in Executor
    dii.ctx.reset(dii_token)
  File "/home/programripper/PycharmProjects/test/__pypackages__/3.10/lib/graia/broadcast/utilles.py", line 60, in reset
    return self.current_ctx.reset(token)
ValueError: <Token var=<ContextVar name='bcc_dii' at 0x7fdf43259a30> at 0x7fdf4047a940> was created in a different Context

The "free-flying" task is collected by gc, and trigger finally. But ctx.reset() raised a ValueError, because the token "was created in a different Context".

Though I didn't test, I suppose any exception or other else that trigger except or finally outside a corotine will suffer from this problem.

Your environment

  • CPython versions tested on: 3.8.12, 3.9.9, 3.10.3, 3.11.0b3
  • Operating system and architecture: Linux, Windows

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions