Skip to content

Warn on finally: cancel_scope.cancel() #365

Open

Description

@Zac-HD

Like async102 and async120, this is to address the concerns discussed in python-trio/trio#455. (also: should the rules docs link there?)

with CancelScope() as scope:
    try:
        ...  # whatever sync and async code here
    finally:
        scope.cancel()
        # You'd hope this just stops any ongoing work, but it swallows exceptions too!

In most cases the solution is simply to scope.cancel() without the try/finally block - structured concurrency means we reliably clean up on exceptions anyway. For some, the cancel scope is entirely unnecessary, and you could call e.g. recv_chan.close() instead of cancelling.

Activity

  1. gpshead commented on Apr 1, 2025

    @gpshead

    This feels ironically like a harder for a human to notice version of the https://peps.python.org/pep-0765/ return-within-finally anti-pattern. 😅

  2. jakkdl commented on Apr 1, 2025

    @jakkdl
    Member

    hmm, that's a weirdly indirect rule. The proper fix here would of course be that everybody writes correct __aexit__ handlers, but given that python-trio/trio#1559 remains open and __aexit__ generally being insanely difficult I suppose we might as well add another layer of redundancy with this.

  3. Zac-HD commented on Apr 1, 2025

    @Zac-HD
    MemberAuthor

    I think specifically for AsyncResource I'd be happier with proposal (2) of python-trio/trio#1559 (comment), but this isn't limited to AsyncResource - as in the top post of this issue, you can get the same effect with raw CancelScope, and as in that comment by @njsmith I don't like teleporting exceptions around.

    Hmm. The original motivation had an async context manager between the cancel scope and the try/finally; I think you actually need that to trigger my problem, because as you note it's really about __aexit__-while-cancelled. So the finally: is a bit of a red herring; what we actually care about is cancelling when an exception is propagating and going to hit an __aexit__.

    Maybe we should work out an endorsed design for __aexit__ instead, and how to lint for that?

  4. jakkdl commented on Apr 1, 2025

    @jakkdl
    Member

    Hmm. The original motivation had an async context manager between the cancel scope and the try/finally; I think you actually need that to trigger my problem, because as you note it's really about __aexit__-while-cancelled. So the finally: is a bit of a red herring; what we actually care about is cancelling when an exception is propagating and going to hit an __aexit__.

    yeah it took me a second to figure out how your repro could ever cause swallowed exceptions, and I do indeed think you need __aexit__

    Maybe we should work out an endorsed design for __aexit__ instead, and how to lint for that?

    We have @agronholm's recent suggestion in the internal repo, so if we get a version of that into trio & anyio we could tell users to use mix-in + __acontextmanager__ instead.
    Trying to detect if an __aexit__ follows an endorsed design sounds non-trivial, but warning on all uses of it would be very noisy, and it's a non-trivial rewrite for the user that I don't think we could offer an autofix for, so it might have to start out as an optional rule.

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

    new rulenew rule ideapostponedLow priority, blocked, or similar.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions