-
Notifications
You must be signed in to change notification settings - Fork 880
Deadlock/freeze when awaiting methods related to screen management in @on message handler #5596
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
Comments
We found the following entries in the FAQ which you may find helpful:
Feel free to close this issue if you found an answer in the FAQ. Otherwise, please give us a little time to review. This is an automated reply, generated by FAQtory |
Your first and second examples create a deadlock by awaiting Your third example is doing some things with regards to modes which seems odd to me, and I am reluctant to comment on until I understand your use case. If I understood what problem you think you are solving, I suspect I could offer an alternative solution which would negate the need for "reseting modes". To understand your use case fully, I will need to know why you are doing it that way. Please describe that problem for me, rather than the solution you are implementing. |
Yeah, that's exactly why I created this issue. This is the root of the problem. Awaiting in a message handler creates a deadlock, while awaiting in a binding handler works just fine - I think it should work just fine in both cases. Currently, it leads to a hard-to-notice bug, which is a major one (because it's a deadlock) and prevents doing some things via button widget while it’s just fine to do it via key binding. Another example with new Button action parameterfrom __future__ import annotations
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, Static
class FirstScreen(Screen):
def compose(self) -> ComposeResult:
yield Static("First screen")
yield Button("Push second screen")
@on(Button.Pressed)
def push_second_screen(self) -> None:
self.app.push_screen(SecondScreen())
class SecondScreen(Screen):
BINDINGS = [
Binding("d", "do_something", "Do something"),
]
def compose(self) -> ComposeResult:
yield Static("Second screen")
yield Button("Do something", action="do_something")
yield Footer()
async def action_do_something(self) -> None:
"""It's fine to call this from key bindings."""
await self.app.pop_screen()
self.notify("Something that requires the screen to be already popped aka. await screen replacement.")
class MyApp(App):
def on_mount(self) -> None:
self.push_screen(FirstScreen())
MyApp().run() This is also the same issue (which was workarounded): This might also be the root issues of these:
What do you mean by not awaiting pop_screen while as I said, I need to be sure, that screen was changed before proceeding with further instructions. When we have to await it and make sure that the previous screen was popped, this just can’t
AFAIK, this can be achieved only by awaiting the I also know the workaround about awaiting in a different task ( I don’t think there’s a proper fix/way to do it but rather a workaround that introduces another issue.
The Textual mode switching via So, let’s assume I’m in main mode abd in my and I switch mode I don’t want that behaviour. I want the previous “mode” to be discarded so when I switch So then, when I come back, I think it is a matter of choice. I think some apps will want to keep the previous mode intact when changing mode to another one, so they can go back to the same place when switching mode again, but others will want to go back to the initial screen from the mode. In my project, I need to switch always to the "fresh" mode - so initial screen of it. Another problem is that leaving many modes/screens existing leads to their refreshing (reactive) even though they are not the currently displayed screen/mode. So that's why I have implemented |
It is a gotcha, but it is not a bug in Textual. You can create such deadlocks in asyncio without textual. Awaiting pop_screen means waiting until all messages in a screen have been processed. Logically this cannot happen if your message handler is blocking. There is no way to make it "work just fine" without breaking what it means to await the screen. Textual is not magic. The best Textual can do is raise a helpful error when you attempt this, which is what The solution is to not await pop_screen or dismiss. That's why awaiting is optional. You can also run your handler concurrently with with a worker. If you have race conditions, this can be solved through synchronization primitives which you will find in the asyncio documentation. The solution(s) I described are canonical, and frankly perfectly fine. If you still don't like it, I'm afraid I can't help you any further. With regards to your switching mode issue. I'm almost certain I don't follow fully, but perhaps I am getting closer. I will close this issue, and I suggest you open a discussion (not an issue). Please explain what you are attempting to solve from the user's perspective. In other words, avoid mentioning modes, or screen stacks etc. Focus on the user's experience. |
Don't forget to star the repository! Follow @textualizeio for Textual updates. |
This issue is related to #5008 (comment) - problem is still valid - awaiting pop_screen freezes application. Given workaround solution by Will is not satisfying - it can cause other issues like race conditions. I am running out of solutions for this. Of course executing the same code by bindings works without any problems. Popping screen should work not matter what caused execution of code - reaction for binding in
action
method or button handle in@on(Button.Pressed)
.Here is MRE which shows the problem:
A bit more complicated, but real example:
In my application also faced similar problem while switching modes. I have dashboard (main) mode and config mode. Also got some global buttons in header which change modes if necessary - dashboard, config and cart (which is in dashboard mode, additionally pushes next screen). This time application freezes probably while resetting old mode (need to remove and add it to drop all screens from screen stack). I think this is connected with pop_screen problem. Both are screen management.
The text was updated successfully, but these errors were encountered: