textual icon indicating copy to clipboard operation
textual copied to clipboard

Textual crashes on application error

Open mhils opened this issue 3 years ago • 2 comments

When my custom application code raises an exception in on_mount, Textual clears the screen (which hides the original error) and then crashes itself.

When debugging #247 I found that the way Textual crashes is rather unsatisfying way upon exit (Screenshot 1). Scrolling up reveals parts of the beautifully rendered error message, which unfortunately is cut off (Screenshot 2).

Screenshot 1 Screenshot 2
image image

Based on my limited testing this affects both Windows and Linux, both on main and the css branch[^1].

[^1]: The simple example on the css branch only renders a black screen at the moment, but the crash behavior is the same.

Steps to reproduce

Repro: Insert raise RuntimeError in the get_markdown callback in examples/simple.py: https://github.com/Textualize/textual/blob/cfefb36ee4dcd4ac3b5c5bde5c040ebeb55f11e6/examples/simple.py#L30-L35

Additional Information

The amount of info that is cut off depends on the terminal size, presumably because textual clears the current screen upon exit and everything above the fold stays.

terminal width 120x40
PS C:\Users\user\git\textual\examples> python .\simple.py
╭───────────────────────────────────────── Traceback (most recent call last) ──────────────────────────────────────────╮
│                                                                                                                      │
│ C:\Python310\lib\site-packages\textual\message_pump.py:306 in on_callback                                            │
│                                                                                                                      │
│   303 │   │   return await self.post_message(message)                                                                │
│   304 │                                                                                                              │
│   305 │   async def on_callback(self, event: events.Callback) -> None:                                               │
│ ❱ 306 │   │   await event.callback()                                                                                 │
│   307 │                                                                                                              │
│   308 │   def emit_no_wait(self, message: Message) -> bool:                                                          │
│   309 │   │   if self._parent:                                                                                       │
│                                                                                                                      │
│ ╭───────────────────────────────────────────────────── locals ─────────────────────────────────────────────────────╮ │

Traceback (most recent call last): File "C:\Python310\lib\site-packages\textual_timer.py", line 89, in _run async def _run(self) -> None: asyncio.exceptions.CancelledError

During handling of the above exception, another exception occurred:

Traceback (most recent call last): File "C:\Python310\lib\site-packages\textual\app.py", line 204, in run_app await app.process_messages() File "C:\Python310\lib\site-packages\textual\app.py", line 311, in process_messages await self.animator.stop() File "C:\Python310\lib\site-packages\textual_animator.py", line 119, in stop await self._timer.stop() File "C:\Python310\lib\site-packages\textual_timer.py", line 79, in stop await self._task asyncio.exceptions.CancelledError

During handling of the above exception, another exception occurred:

Traceback (most recent call last): File "C:\Users\user\git\textual\examples\simple.py", line 38, in MyApp.run(title="Simple App", log="textual.log") File "C:\Python310\lib\site-packages\textual\app.py", line 206, in run asyncio.run(run_app()) File "C:\Python310\lib\asyncio\runners.py", line 44, in run return loop.run_until_complete(main) File "C:\Python310\lib\asyncio\base_events.py", line 641, in run_until_complete return future.result() asyncio.exceptions.CancelledError

terminal width 80x40
PS C:\Users\user\git\textual\examples> python .\simple.py
╭───────────────────── Traceback (most recent call last) ──────────────────────╮
│                                                                              │
│ C:\Python310\lib\site-packages\textual\message_pump.py:306 in on_callback    │
│                                                                              │
│   303 │   │   return await self.post_message(message)                        │
│   304 │                                                                      │
│   305 │   async def on_callback(self, event: events.Callback) -> None:       │
│ ❱ 306 │   │   await event.callback()                                         │
│   307 │                                                                      │
│   308 │   def emit_no_wait(self, message: Message) -> bool:                  │
│   309 │   │   if self._parent:                                               │
│                                                                              │
│ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
│ │ event = Callback(                                                        │ │
│ │         │   callback=functools.partial(.get_markdown at 0x000001D291BED630>,     │ │
│ │         'richreadme.md')                                                 │ │

Traceback (most recent call last): File "C:\Python310\lib\site-packages\textual_timer.py", line 89, in _run async def _run(self) -> None: asyncio.exceptions.CancelledError

During handling of the above exception, another exception occurred:

Traceback (most recent call last): File "C:\Python310\lib\site-packages\textual\app.py", line 204, in run_app await app.process_messages() File "C:\Python310\lib\site-packages\textual\app.py", line 311, in process_messages await self.animator.stop() File "C:\Python310\lib\site-packages\textual_animator.py", line 119, in stop await self._timer.stop() File "C:\Python310\lib\site-packages\textual_timer.py", line 79, in stop await self._task asyncio.exceptions.CancelledError

During handling of the above exception, another exception occurred:

Traceback (most recent call last): File "C:\Users\user\git\textual\examples\simple.py", line 38, in MyApp.run(title="Simple App", log="textual.log") File "C:\Python310\lib\site-packages\textual\app.py", line 206, in run asyncio.run(run_app()) File "C:\Python310\lib\asyncio\runners.py", line 44, in run return loop.run_until_complete(main) File "C:\Python310\lib\asyncio\base_events.py", line 641, in run_until_complete return future.result() asyncio.exceptions.CancelledError

terminal width 80x20
PS C:\Users\user\git\textual\examples> python .\simple.py
╭───────────────────── Traceback (most recent call last) ──────────────────────╮
│                                                                              │
│ C:\Python310\lib\site-packages\textual\message_pump.py:306 in on_callback    │
│                                                                              │
│   303 │   │   return await self.post_message(message)                        │
│   304 │                                                                      │
│   305 │   async def on_callback(self, event: events.Callback) -> None:       │
│ ❱ 306 │   │   await event.callback()                                         │
│   307 │                                                                      │
│   308 │   def emit_no_wait(self, message: Message) -> bool:                  │
│   309 │   │   if self._parent:                                               │
│                                                                              │
│ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
│ │ event = Callback(                                                        │ │
│ │         │   callback=functools.partial(.get_markdown at 0x000001CA9AA1D630>,     │ │
│ │         'richreadme.md')                                                 │ │
│ │         )                                                                │ │
│ │  self = MyApp(title='Simple App')                                        │ │
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
│ C:\Users\user\git\textual\examples\simple.py:32 in get_markdown              │
│                                                                              │
│   29 │   │                                                                   │
│   30 │   │   async def get_markdown(filename: str) -> None:                  │
│   31 │   │   │   with open(filename, "r") as fh:                             │
│ ❱ 32 │   │   │   │   readme = Markdown(fh.read(), hyperlinks=True)           │
│   33 │   │   │   await body.update(readme)                                   │
│   34 │   │                                                                   │
│   35 │   │   await self.call_later(get_markdown, "richreadme.md")            │
│                                                                              │
│ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
│ │     body = ScrollView(name='ScrollView#1')                               │ │
│ │       fh = <_io.textiowrapper name="richreadme.md" mode="r" encoding="cp1252">                                            │ │
│ │ filename = 'richreadme.md'                                               │ │
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
│                                                                              │

Traceback (most recent call last): File "C:\Python310\lib\site-packages\textual_timer.py", line 89, in _run async def _run(self) -> None: asyncio.exceptions.CancelledError

During handling of the above exception, another exception occurred:

Traceback (most recent call last): File "C:\Python310\lib\site-packages\textual\app.py", line 204, in run_app await app.process_messages() File "C:\Python310\lib\site-packages\textual\app.py", line 311, in process_messages await self.animator.stop() File "C:\Python310\lib\site-packages\textual_animator.py", line 119, in stop await self._timer.stop() File "C:\Python310\lib\site-packages\textual_timer.py", line 79, in stop await self._task asyncio.exceptions.CancelledError

During handling of the above exception, another exception occurred:

Traceback (most recent call last): File "C:\Users\user\git\textual\examples\simple.py", line 38, in MyApp.run(title="Simple App", log="textual.log") File "C:\Python310\lib\site-packages\textual\app.py", line 206, in run asyncio.run(run_app()) File "C:\Python310\lib\asyncio\runners.py", line 44, in run return loop.run_until_complete(main) File "C:\Python310\lib\asyncio\base_events.py", line 641, in run_until_complete return future.result() asyncio.exceptions.CancelledError

mhils avatar Feb 01 '22 10:02 mhils

I've noticed the same thing. It's not clear to me if debugging in Textual is so difficult because of Textual itself, or because of the whole asyncio thing. But it makes for slow going!

jeffwright13 avatar Feb 10 '22 15:02 jeffwright13

Seeing the same thing on my end @mhils. Haven't yet gotten a chance to dig into the root cause behind this - but guessing it's some premature runloop termination that's also short circuiting the basic drawing layer setup. A temporary workaround is to allow the graphics code to paint before our mounting code interrupts it:

from asyncio import sleep

    async def on_mount(self, event: events.Mount) -> None:
        """Create and dock the widgets."""
        await sleep(0.001)

        # A scrollview to contain the markdown file
        body = ScrollView(gutter=1)
        ...
Screen Shot 2022-09-16 at 9 42 12 AM

piercefreeman avatar Sep 16 '22 13:09 piercefreeman

https://github.com/Textualize/textual/wiki/Sorry-we-closed-your-issue

willmcgugan avatar Oct 25 '22 09:10 willmcgugan

Did we solve your problem?

Glad we could help!

github-actions[bot] avatar Oct 25 '22 09:10 github-actions[bot]