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

CancelledError when doing a self.remove from a timer (in Python 3.7 but not Python 3.11) #2854

Closed
davep opened this issue Jun 28, 2023 · 4 comments · Fixed by #2895
Closed
Assignees
Labels
bug Something isn't working Task

Comments

@davep
Copy link
Contributor

davep commented Jun 28, 2023

Having run into this while working on notifications, and puzzled over this with @willmcgugan when first seen, this is me isolating the problem into its own code/issue for further investigation.

For anyone looking at this, I've already uncovered what might be one really important bit of information: THIS DEPENDS ON PYTHON VERSION. I get the error I'm about to describe when working with Python 3.7, but not with Python 3.11 (I've yet to test 3.8, 3.9 or 3.10).

Given this code:

from textual import on
from textual.app import App, ComposeResult
from textual.containers import Vertical, VerticalScroll
from textual.widgets import Button, Label

class SelfRemovingLabel(Label):

    def on_mount(self) -> None:
        self.set_timer(2, self.remove)

class RemoveOnTimerApp(App[None]):

    CSS = """
    Label {
        margin-bottom: 1;
        width: 1fr;
        border: solid $warning;
        background: $warning 20%;
    }
    """

    def compose(self) -> ComposeResult:
        with Vertical():
            yield Button("Add self-removing label")
            yield VerticalScroll()

    @on(Button.Pressed)
    def add_ephemeral_label(self):
        self.query_one(VerticalScroll).mount(SelfRemovingLabel("I will remove myself!"))

if __name__ == "__main__":
    RemoveOnTimerApp().run()

with Python 3.7 and the latest Textual, if you mash the button and wait for a moment for a Label to remove itself, you'll get the following error:

╭────────────────────────────────────────────────────────── Traceback (most recent call last) ───────────────────────────────────────────────────────────╮
│ /Users/davep/develop/python/textual/src/textual/await_remove.py:33 in await_prune                                                                      │
│                                                                                                                                                        │
│   30 │   def __await__(self) -> Generator[None, None, None]:                                                                                           │
│   31 │   │   async def await_prune() -> None:                                                                                                          │
│   32 │   │   │   """Wait for the prune operation to finish."""                                                                                         │
│ ❱ 33 │   │   │   await self.finished_flag.wait()                                                                                                       │
│   34 │   │                                                                                                                                             │
│   35 │   │   return await_prune().__await__()                                                                                                          │
│   36                                                                                                                                                   │
│                                                                                                                                                        │
│ ╭──────────────────────────── locals ─────────────────────────────╮                                                                                    │
│ │ self = <textual.await_remove.AwaitRemove object at 0x1020fed88> │                                                                                    │
│ ╰─────────────────────────────────────────────────────────────────╯                                                                                    │
│                                                                                                                                                        │
│ /Users/davep/.pyenv/versions/3.7.16/lib/python3.7/asyncio/locks.py:293 in wait                                                                         │
│                                                                                                                                                        │
│   290 │   │   fut = self._loop.create_future()                                                                                                         │
│   291 │   │   self._waiters.append(fut)                                                                                                                │
│   292 │   │   try:                                                                                                                                     │
│ ❱ 293 │   │   │   await fut                                                                                                                            │
│   294 │   │   │   return True                                                                                                                          │
│   295 │   │   finally:                                                                                                                                 │
│   296 │   │   │   self._waiters.remove(fut)                                                                                                            │
│                                                                                                                                                        │
│ ╭───────────────────────── locals ─────────────────────────╮                                                                                           │
│ │  fut = <Future cancelled>                                │                                                                                           │
│ │ self = <asyncio.locks.Event object at 0x1020ff1c8 [set]> │                                                                                           │
│ ╰──────────────────────────────────────────────────────────╯                                                                                           │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
CancelledError

If SelfRemovingLabel is modified to add a level of indirection to the remove, for example:

class SelfRemovingLabel(Label):

    def on_mount(self) -> None:
        self.set_timer(2, self._my_remove)

    def _my_remove(self) -> None:
        self.remove()

it works fine.

@davep davep added bug Something isn't working Task labels Jun 28, 2023
davep added a commit to davep/textual-sandbox that referenced this issue Jun 28, 2023
davep added a commit to davep/textual that referenced this issue Jun 28, 2023
See Textualize#2854 -- turns out that it's
still an issue, but on Python 3.7 but not Python 3.11 (as mentioned in the
linked issue, I've not tested other versions yet).
@willmcgugan
Copy link
Collaborator

Weird one!

@rodrigogiraoserrao
Copy link
Contributor

I managed to reproduce the issue in Python 3.7.
I could not reproduce the issue in Python 3.8, 3.9, 3.10, and 3.11.
Thus, it looks like it is something with Python 3.7

Waiting for resolution of #2883 as that may mean there is nothing to do here.

@willmcgugan
Copy link
Collaborator

We won't be dropping 3.7 any time soon, so we may need a workaround.

I'm guessing we need to wrap that wait and catch Cancelled Errors. You might want to look at the implementation of locks.py in 3.8 and above.

@github-actions
Copy link

github-actions bot commented Jul 6, 2023

Don't forget to star the repository!

Follow @textualizeio for Textual updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working Task
Projects
None yet
3 participants