Skip to content

Regression: unittest.IsolatedAsyncioTestCase in 3.11 is too picky about what it will run with the event loop #101486

Closed as not planned

Description

@zware

In previous versions, _callMaybeAsync(self, func, /, *args, **kwargs) would run result = func(*args, **kwargs) and then run the result with the event loop if inspect.isawaitable(result) returned True. In 3.11 inspect.iscoroutinefunction(func) is checked ahead of time instead, resulting in a warning about a coroutine not being awaited after trying to run func(*args, **kwargs) synchronously. This prevents using anything other than an async def-defined coroutine for test methods or cleanup functions1, the latter of which may not be defined in local code. This can be worked around by wrapping any known awaitable in a dummy async def _wrapper(aw): return await aw, but that's rather inconvenient and not a pleasant UX.

I have a "fix" for this that I don't consider to be viable that I'll nevertheless attach as a draft PR for discussion. It gives the behavior I would prefer to see in unittest and passes all existing tests, but relies on an ugly hack in asyncio to defeat the non-reentrancy of contextvars.Context.run. Ideally, either Context.run would allow recursive calls (by not running the entrance and exit routines if the context is already entered?), or would expose the underlying ctx_entered flag to allow LBYL when calling Context.run. I don't have a great understanding of contextvars, so I'm not sure how any of these options might break things or if there's a better option that's obvious to someone who knows what's going on here :)

Linked PRs

Footnotes

  1. which when initially added explicitly supported exotic awaitables, see https://github.com/python/cpython/blob/76efcb40930d1584e8706f015d0e5475fb16acb5/Lib/unittest/async_case.py#L53-L58 ↩

Activity

  1. added
    type-bugAn unexpected behavior, bug, or error
    stdlibStandard Library Python modules in the Lib/ directory
    3.11only security fixes
    3.12only security fixes
    on Feb 1, 2023
  2. kumaraditya303 commented on Feb 3, 2023

    @kumaraditya303
    Contributor

    Honestly, this whole thing needs a rewrite with a better design which relies less on the event loop details but I don't know how feasible that is.

    IIRC there were some recent changes to inspect.iscoroutinefunction which might make this easier, @gvanrossum?

  3. gvanrossum commented on Feb 3, 2023

    @gvanrossum
    Member

    Yeah, there's now an inspect.markcoroutinefunction decorator that you can use to make a sync function that returns something async appear async to whoever calls inspect.iscoroutinefunction on it.

    Unfortunately there seems a lot of use of this outside the stdlib so a full rewrite will probably break other people's dependencies (no matter how brittle) on the current implementation. There's some documentation but I'm not sure how much people just depend on what happens to work and will get disappointed if we change anything. :-(

  4. kumaraditya303 commented on Jul 4, 2025

    @kumaraditya303
    Contributor

    The associated PR is closed and 3.11 is in security fixes only; the docs state that addAsyncCleanup only accept coroutines, is there anything to do here or should we close it now @zware ?

  5. zware commented on Jul 5, 2025

    @zware
    MemberAuthor

    The PR was never considered to be a reasonable fix, I've long since worked around the issue and forgotten how, and nobody else seems to have complained, so I'll give up and live with whatever my workaround was :)

  6. moved this from Todo to Done in asyncioon Jul 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.11only security fixes3.12only security fixesstdlibStandard Library Python modules in the Lib/ directorytopic-asynciotype-bugAn unexpected behavior, bug, or error

    Projects

    • Status
      Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions