Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CancelScope.cancelled_caught #267

Closed
wants to merge 2 commits into from

Conversation

johnzeringue
Copy link

Added CancelScope.cancelled_caught to match the Trio API. Relevant
Trio documentation
here.

Closes #257

@agronholm
Copy link
Owner

It seems that there are no tests for cases when cancelled_caught differs from cancel_called.

@johnzeringue
Copy link
Author

@agronholm I added asserts for when .cancel() has been called but you're still inside the cancel scope's context manager. I think that's the only time that cancelled_caught and cancel_called should differ.

tests/test_taskgroups.py Outdated Show resolved Hide resolved
@johnzeringue johnzeringue force-pushed the cancelled-caught branch 3 times, most recently from 481a087 to 891d5f8 Compare May 7, 2021 14:43
.gitignore Outdated Show resolved Hide resolved
Copy link
Owner

@agronholm agronholm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some questions, and code style nits.

tests/test_taskgroups.py Show resolved Hide resolved
tests/test_taskgroups.py Outdated Show resolved Hide resolved
tests/test_taskgroups.py Outdated Show resolved Hide resolved
tests/test_taskgroups.py Outdated Show resolved Hide resolved
tests/test_taskgroups.py Outdated Show resolved Hide resolved
tests/test_taskgroups.py Outdated Show resolved Hide resolved
tests/test_taskgroups.py Outdated Show resolved Hide resolved
tests/test_taskgroups.py Outdated Show resolved Hide resolved
@johnzeringue
Copy link
Author

Okay I think I've replied to or fixed everything

Copy link
Owner

@agronholm agronholm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments, and pointed out a problematic logic branch.

Comment on lines 339 to 340
elif self._cancel_called:
self._cancelled_caught = True
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does it set cancelled_caught to True here, when it clearly didn't catch a CancelledError? If a scope is cancelled but the cancellation exception is caught inside of it, cancelled_caught should be False.

Case in point:

import trio

async def task(*, task_status):
    with trio.CancelScope() as scope:
        task_status.started(scope)
        try:
            await trio.sleep_forever()
        except trio.Cancelled:
            pass

async def main():
    async with trio.open_nursery() as nursery:
        scope = await nursery.start(task)
        scope.cancel()

    print(scope.cancel_called)
    print(scope.cancelled_caught)

trio.run(main)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added that code based on a misunderstanding of this conversation about whether cancelled_caught is the best variable name. That code served a purpose; it fixed an error in test_cancel_cascade where tg.cancel_scope.cancelled_caught was False only for the asyncio backends.

Your comment here led me down a rabbit hole trying to understand how a misguided change had fixed a broken test case. The short story is that self.cancel_scope.__exit__ is the first thing done in TaskGroup.__aexit__, so in something like

async def task():
    await sleep(1)

async with create_task_group() as tg:
    tg.start_soon(task)

tg.cancel_scope.__exit__ is called before the child task finishes. In CancelScope.__exit__, I was deciding that the child task wouldn't throw a CancelledError while it was still running!

I think this sequence of events causes issues in the current code too. For example:

async def task(scope):
    await sleep(1)

    with scope:
        # No RuntimeError!
        ...

async with create_task_group() as tg:
    tg.start_soon(task, tg.cancel_scope)

Anyways, I committed a crude fix based on the approach in Trio (gather exceptions from all child tasks, then close the cancel scope). It's still failing one of the asyncio tests, but I think that's because the crude fix calls CancelScope.cancel before calling CancelScope.__exit__ and we use CancelScope._cancel_called to detect native cancellation.

I'll have more time in a couple days to polish it up, and will address your other comments then as well.

Copy link
Owner

@agronholm agronholm May 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tried to do the same refactoring quite a few times and it always ended in failure. Let's see if your effort will fare better.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I pushed an attempt at that refactoring. It wasn't too terrible, so I'm afraid I might have missed something 🙈

tests/test_taskgroups.py Outdated Show resolved Hide resolved
tests/test_taskgroups.py Show resolved Hide resolved
tests/test_taskgroups.py Outdated Show resolved Hide resolved
@johnzeringue johnzeringue force-pushed the cancelled-caught branch 9 times, most recently from 1d19a7d to ec3d18c Compare May 12, 2021 19:14
@agronholm
Copy link
Owner

Do the test suites pass for you locally?

@johnzeringue
Copy link
Author

Tests do pass locally, although until a final tweak, I was seeing issues with test_shielded_cancel_sleep_time being flakey locally.

Added `CancelScope.cancelled_caught` to match the Trio API. Relevant
Trio documentation
[here](https://trio.readthedocs.io/en/stable/reference-core.html#trio.CancelScope.cancelled_caught).

Refactored `TaskGroup.__aexit__` to match Trio's structured concurrency
model.

Closes agronholm#257
@agronholm
Copy link
Owner

This PR includes changes I'm not comfortable having in the (imminent) v3.1 release, so I'm deferring this to v3.2, minimum.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement CancelScope.cancelled_caught
3 participants