Skip to content

Change base class for futures.CancelledError #76709

Closed

Description

@socketpair
BPO 32528
Nosy @gvanrossum, @asvetlov, @cjerdonek, @socketpair, @dimaqq, @1st1, @achimnol, @miss-islington, @bmerry, @JustAnotherArchivist
PRs
  • bpo-32528: Make asyncio.CancelledError a BaseException. #13528
  • bpo-32528: Document the change in inheritance of asyncio.CancelledError #21474
  • [3.9] bpo-32528: Document the change in inheritance of asyncio.CancelledError (GH-21474) #21475
  • [3.8] bpo-32528: Document the change in inheritance of asyncio.CancelledError (GH-21474) #21476
  • [3.9] bpo-32528: Document the change in inheritance of asyncio.CancelledError (GH-21474) #21606
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = <Date 2019-05-27.12:45:50.368>
    created_at = <Date 2018-01-10.16:31:15.055>
    labels = ['3.8', 'type-feature', 'library', 'expert-asyncio']
    title = 'Change base class for futures.CancelledError'
    updated_at = <Date 2020-07-24.14:19:20.996>
    user = 'https://github.com/socketpair'

    bugs.python.org fields:

    activity = <Date 2020-07-24.14:19:20.996>
    actor = 'miss-islington'
    assignee = 'none'
    closed = True
    closed_date = <Date 2019-05-27.12:45:50.368>
    closer = 'yselivanov'
    components = ['Library (Lib)', 'asyncio']
    creation = <Date 2018-01-10.16:31:15.055>
    creator = 'socketpair'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 32528
    keywords = ['patch']
    message_count = 21.0
    messages = ['309772', '309775', '309776', '309777', '309779', '309796', '309799', '309800', '309894', '317705', '326274', '326275', '337839', '343618', '343619', '373166', '373510', '373511', '373650', '373940', '374185']
    nosy_count = 11.0
    nosy_names = ['gvanrossum', 'gustavo', 'asvetlov', 'chris.jerdonek', 'socketpair', 'Dima.Tisnek', 'yselivanov', 'achimnol', 'miss-islington', 'bmerry', 'JustAnotherArchivist']
    pr_nums = ['13528', '21474', '21475', '21476', '21606']
    priority = 'normal'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'enhancement'
    url = 'https://bugs.python.org/issue32528'
    versions = ['Python 3.8']

    Activity

    1. socketpair commented on Jan 10, 2018

      socketpairmannequin
      MannequinAuthor

      I have discoverd one very ugly pattern connected with asyncio. Many times I see code like this:

      try:
      await something()
      except Exception:
      log.error('Opertaion failed -- will retry later.')

      Seems, everything is fine, but asyncio.CancelledError unintentionally
      also suppressed in that code. So, sometimes coroutines are not cancellable.

      In order to mitigate thi, we had to write:

      try:
      await something()
      except CancelledError:
      raise
      except Exception:
      log.error('Opertaion failed. will retry later.')

      So, what I propose: Basically is to change base class for asyncio.CancelledError
      from Exception (yes, I know concurrent.futures and it's Error class) to BaseException.

      Just like SystemExit and other SPECIAL exceptions.

      Yes, I know that it would be incompatible change. But I suspect that impact will be minimal. Documentation for concurrent.futures and asyncio does not say that this exception is derived from Exception.

    2. 1st1 commented on Jan 10, 2018

      @1st1
      Member

      This is a backwards incompatible change. IMO it's too late to change this.

    3. socketpair commented on Jan 10, 2018

      socketpairmannequin
      MannequinAuthor

      Will you accept PR if I fix that ?

      I think we may merge that in Python 3.8

      Who can also judge us? @asvetlov, what do you think about my idea ?

    4. 1st1 commented on Jan 10, 2018

      @1st1
      Member

      While I understand the reasons for the proposed change, I'd still be -1 for it. Solely on the basis of "we don't know how much this change will break, but it will surely break something in very subtle ways".

      Another problem is that asyncio currently doesn't handle BaseExceptions that well.

      I'll put Guido in the nosy list, maybe he'll have a different opinion on this.

    5. gvanrossum commented on Jan 10, 2018

      @gvanrossum
      Member

      I agree with Yury, this feels too risky to consider. The "except Exception:
      <log>" code is at fault.

    6. socketpair commented on Jan 11, 2018

      socketpairmannequin
      MannequinAuthor

      @gvanrossum

      More real code:

      async def xxxx():
          while True:
              try:
                  result = await download()
                  handle_result(result)
              except Exception as e:
                  log.warning('Fail..%r', e)
              await asyncio.sleep()

      Why sucha a code is fault ?

    7. asvetlov commented on Jan 11, 2018

      @asvetlov
      Contributor

      Inheritance from Exception is very annoying, I saw issues with unexpected suppressing CancelledError many times.

      Even experienced people constantly forget to add a separate except asyncio.CancelledError clause everywhere.

      But proposed change is backward incompatible, sure.

    8. asvetlov commented on Jan 11, 2018

      @asvetlov
      Contributor

      Honestly I have no strong opinion.

      Correct code should not be affected, if somebody want to handle task cancellation explicitly -- he already have except CancelledError in his code.

      What are use cases for intentional catching Exception for task cancellation prevention?

      Could we add a warning for this case without base exception class change? I don't see how to do it but maybe somebody has an idea?

    9. achimnol commented on Jan 13, 2018

      achimnolmannequin
      Mannequin

      I strongly agree to have discretion of CancelledError and other general exceptions, though I don't have concrete ideas on good unobtrusive ways to achieve this.

      If I write my codes carefully I could control most of cancellation explicitly, but it is still hard to control it in 3rd-party libraries that I depend on. Often they just raise random errors, or CancelledError is swallowed.

      Also it would be nice to have some documentation and examples on how to write "cancellation-friendly" coroutine codes.

    10. 1st1 commented on May 25, 2018

      @1st1
      Member

      Closing this issue as I, personally, don't see this happening and there's no point in keeping it open.

    11. 2 remaining items

    12. 1st1 commented on Sep 24, 2018

      @1st1
      Member

      Closing this issue as I, personally, don't see this happening and there's no point in keeping it open.

      Actually, Andrew and I changed our opinion on this, so I'm re-opening the issue.

      After visiting three conferences this summer and talking to asyncio users, it seems that this is a very serious pitfall. At least 8 different people shared stories about really hard to debug problems caused by "except Exception" code blocking cancellation.

      I now think we should fix this and make CancelledError a BaseException. Doing that isn't as straightforward as it seems as we have to first fix how asyncio handles BaseExceptions (my next ToDo).

    13. reopened this on Sep 24, 2018
    14. dimaqq commented on Mar 13, 2019

      dimaqqmannequin
      Mannequin

      ping

    15. 1st1 commented on May 27, 2019

      @1st1
      Member

      New changeset 431b540 by Yury Selivanov in branch 'master':
      bpo-32528: Make asyncio.CancelledError a BaseException. (GH-13528)
      431b540

    16. 1st1 commented on May 27, 2019

      @1st1
      Member

      Done. 🤞 we don't regret this.

    17. bmerry commented on Jul 6, 2020

      bmerrymannequin
      Mannequin

      FYI this has just bitten me after updating my OS to one that ships Python 3.8. It is code that was written with asyncio cancellation in mind and which expected CancelledError to be caught with "except Exception" (the exception block unwound incomplete operations before re-raising the exception).

      It's obviously too late to do anything about Python 3.8, but I'm mentioning this as a data point in support of having a deprecation period if similar changes are made in future.

      On the plus side, while fixing up my code and checking all instances of "except Exception" I found some places where this change did fix latent cancellation bugs. So I'm happy with the change, just a little unhappy that it came as a surprise.

    18. JustAnotherArchivist commented on Jul 11, 2020

      JustAnotherArchivistmannequin
      Mannequin

      As another datapoint, this also broke some of my code on 3.8 because I was using concurrent.futures.CancelledError rather than asyncio.CancelledError to handle cancelled futures. And I'm certainly not the only one to have done this given that it's mentioned in at least two Stack Overflow answers: https://stackoverflow.com/a/38655063 and https://stackoverflow.com/a/36277556

      While I understand the rationale behind this change, it would've been good to include this inheritance detail in the 3.8 release notes.

    19. gvanrossum commented on Jul 11, 2020

      @gvanrossum
      Member

      Can you send a PR against what’s new 3.8?

      On Fri, Jul 10, 2020 at 20:14 JustAnotherArchivist <report@bugs.python.org>
      wrote:

      JustAnotherArchivist justanotherarchivist@riseup.net added the comment:

      As another datapoint, this also broke some of my code on 3.8 because I was
      using concurrent.futures.CancelledError rather than
      asyncio.CancelledError to handle cancelled futures. And I'm certainly not
      the only one to have done this given that it's mentioned in at least two
      Stack Overflow answers: https://stackoverflow.com/a/38655063 and
      https://stackoverflow.com/a/36277556

      While I understand the rationale behind this change, it would've been good
      to include this inheritance detail in the 3.8 release notes.

      ----------
      nosy: +JustAnotherArchivist


      Python tracker <report@bugs.python.org>
      <https://bugs.python.org/issue32528\>


      --
      --Guido (mobile)

    20. miss-islington commented on Jul 14, 2020

      @miss-islington
      Contributor

      New changeset 2a51818 by JustAnotherArchivist in branch 'master':
      bpo-32528: Document the change in inheritance of asyncio.CancelledError (GH-21474)
      2a51818

    21. miss-islington commented on Jul 19, 2020

      @miss-islington
      Contributor

      New changeset 700cb66 by Miss Islington (bot) in branch '3.8':
      bpo-32528: Document the change in inheritance of asyncio.CancelledError (GH-21474)
      700cb66

    22. miss-islington commented on Jul 24, 2020

      @miss-islington
      Contributor

      New changeset ba07d4a by Miss Islington (bot) in branch '3.9':
      bpo-32528: Document the change in inheritance of asyncio.CancelledError (GH-21474)
      ba07d4a

    23. transferred this issue fromon Apr 10, 2022
    24. Cartroo commented on Jun 18, 2022

      @Cartroo

      I know this is an old issue, but I just wanted to mention the parallel I note with the same change made for GeneratorExit, way back in Python 2.6 — as I recall, the justification then was much the same as the justification now.

      Given the pain of these changes, I wonder if it's worth considering a particularly strenuous review process for exceptions added to any standard library modules to try to make the correct decisions of Exception vs BaseException up front, rather than having to have this debate about fixing them later.

    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.8 (EOL)end of lifestdlibStandard Library Python modules in the Lib/ directorytopic-asynciotype-featureA feature request or enhancement

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions