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

Awaiting pop_screen in @on causes app to freeze #5008

Open
mzebrak opened this issue Sep 16, 2024 · 2 comments
Open

Awaiting pop_screen in @on causes app to freeze #5008

mzebrak opened this issue Sep 16, 2024 · 2 comments

Comments

@mzebrak
Copy link

mzebrak commented Sep 16, 2024

Version: 0.79.1

Adding await to self.app.pop_screen causes app to freeze when triggered via button, but works fine when triggered via key binding.

from __future__ import annotations

from typing import cast

from textual import on
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.screen import Screen
from textual.widgets import Button, Footer, Header, Label


class FirstScreen(Screen):
    def compose(self) -> ComposeResult:
        yield Header()
        yield Label("this is the first screen")
        yield Footer()


class SecondScreen(Screen):
    def compose(self) -> ComposeResult:
        yield Header()
        yield Label("this is the second screen")
        yield Button("Pop back to first screen")
        yield Footer()

    @on(Button.Pressed)
    async def pressed(self) -> None:
        app = cast(MyApp, self.app)
        app.notify("Going back to first screen...")
        await app.action_pop_current_screen()


class MyApp(App):
    BINDINGS = [
        Binding("a", "pop_current_screen", "Pop screen"),  # key binding works just fine with await
        Binding("d", "push", "Push screen"),
    ]

    async def on_mount(self) -> None:
        await self.push_screen(FirstScreen())

    async def action_pop_current_screen(self) -> None:
        while not isinstance(self.screen, FirstScreen):
            await self.pop_screen()  # awaiting causes the bug, removing await fixes it in case of pressing the button

    async def action_push(self) -> None:
        await self.push_screen(SecondScreen())


MyApp().run()
@Textualize Textualize deleted a comment from github-actions bot Sep 16, 2024
@willmcgugan
Copy link
Collaborator

By awaiting the pop_screen, you are waiting for all its messages to be processed. But your button.pressed message handler will never return, because it is waiting for itself to be popped. In other words, a deadlock.

I suspect the best thing for us to do is make it error / warning. To fix it, you can add @work to your action.

@mzebrak
Copy link
Author

mzebrak commented Sep 16, 2024

Putting @work over MyApp.action_pop_current_screen solves the case here. I thought it would be the same as
self.run_worker(app.action_pop_current_screen()) in SecondScreen.pressed, however the second one crashes without any error.

Because real scenario is something more like: #5009
so putting @work over such an App method is a no-go for me.

By awaiting the pop_screen, you are waiting for all its messages to be processed

But in the case of pop_screen, there is no need to wait for all the screen messages to be processed I think? Because this call will destroy this screen anyway, so shouldn't it prioritize it over other messages?

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

No branches or pull requests

2 participants