Giter Club home page Giter Club logo

Comments (25)

arcivanov avatar arcivanov commented on July 30, 2024 1

The MRE can be even further simplified. Recomposition and dynamic structure is irrelevant. The issue pops up the more eagerly the more controls are on the modal screen. The last crash occurred with just two o-ESC sequences.

import sys

from textual import work
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.screen import ModalScreen
from textual.widgets import Label, Select, Footer

exc: Exception | None = None


class StressScreen(ModalScreen):
    BINDINGS = [Binding("escape", "dismiss", "dismiss")]

    def compose(self) -> ComposeResult:
        for idx in range(40):
            yield Select([(f"Foo{idx}", f"foo{idx}"), (f"Bar{idx}", f"bar{idx}")],
                         id=f"independent{idx}_value",
                         value=Select.BLANK)


class StressApp(App):
    BINDINGS = [Binding("o", "modal", "modal")]

    def compose(self) -> ComposeResult:
        yield Label("BAR")
        yield Footer()

    @work(exclusive=True)
    async def action_modal(self) -> None:
        await self.push_screen_wait(StressScreen())

    def _handle_exception(self, error: Exception) -> None:
        global exc
        exc = error
        super()._handle_exception(error)


if __name__ == "__main__":
    try:
        StressApp().run()
    finally:
        if exc:
            sys.excepthook(type(exc), exc, None)
Traceback (most recent call last):
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/message_pump.py", line 540, in _pre_process
    await self._dispatch_message(events.Compose())
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/message_pump.py", line 655, in _dispatch_message
    await self.on_event(message)
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/message_pump.py", line 724, in on_event
    await self._on_message(event)
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/message_pump.py", line 745, in _on_message
    await invoke(method, message)
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/_callback.py", line 85, in invoke
    return await _invoke(callback, *params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/_callback.py", line 47, in _invoke
    result = await result
             ^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/widget.py", line 3720, in _on_compose
    await self._compose()
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/widget.py", line 3734, in _compose
    await self.mount_composed_widgets(widgets)
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/widget.py", line 3747, in mount_composed_widgets
    await self.mount_all(widgets)
          ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/widget.py", line 1016, in mount_all
    await_mount = self.mount(*widgets, before=before, after=after)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/widget.py", line 947, in mount
    raise MountError(f"Can't mount widget(s) before {self!r} is mounted")
textual.widget.MountError: Can't mount widget(s) before StressScreen() is mounted
Traceback (most recent call last):
  File "/home/arcivanov/Documents/src/karellen/app/src/main/python/app/stress.py", line 44, in <module>
    StressApp().run()
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/app.py", line 1620, in run
    asyncio.run(run_app())
  File "/home/arcivanov/.pyenv/versions/3.12.3/lib/python3.12/asyncio/runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/3.12.3/lib/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/3.12.3/lib/python3.12/asyncio/base_events.py", line 687, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/app.py", line 1606, in run_app
    await self.run_async(
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/app.py", line 1572, in run_async
    await app._shutdown()
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/app.py", line 2804, in _shutdown
    await self._close_all()
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/app.py", line 2784, in _close_all
    await self._prune_node(stack_screen)
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/app.py", line 3445, in _prune_node
    raise asyncio.TimeoutError(
TimeoutError: Timeout waiting for [Label(), Footer(), ToastRack(id='textual-toastrack'), Tooltip(id='textual-tooltip')] to close; possible deadlock (consider changing App.CLOSE_TIMEOUT)

from textual.

TomJGooding avatar TomJGooding commented on July 30, 2024 1

For what it's worth, after pulling the latest changes on my fork of Textual, I can't reproduce the issue using the simplified MRE in #4668 (comment) when mashing the o and Esc keys.

from textual.

arcivanov avatar arcivanov commented on July 30, 2024 1

Yep, I removed the textual completely, installed it from the tree and can no longer bang out any of the problem.

from textual.

github-actions avatar github-actions commented on July 30, 2024

Thank you for your issue. Give us a little time to review it.

PS. You might want to check the FAQ if you haven't done so already.

This is an automated reply, generated by FAQtory

from textual.

arcivanov avatar arcivanov commented on July 30, 2024

Another crash looking like it's related (same concurrency issue):

Traceback (most recent call last):
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/message_pump.py", line 542, in _pre_process
    await self._dispatch_message(events.Mount())
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/message_pump.py", line 650, in _dispatch_message
    await self.on_event(message)
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/message_pump.py", line 719, in on_event
    await self._on_message(event)
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/message_pump.py", line 740, in _on_message
    await invoke(method, message)
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/_callback.py", line 85, in invoke
    return await _invoke(callback, *params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/_callback.py", line 45, in _invoke
    result = callback(*params[:parameter_count])
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/widgets/_select.py", line 491, in _on_mount
    self._setup_options_renderables()
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/widgets/_select.py", line 413, in _setup_options_renderables
    option_list = self.query_one(SelectOverlay)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/dom.py", line 1340, in query_one
    return query.only_one() if expect_type is None else query.only_one(expect_type)
           ^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/css/query.py", line 278, in only_one
    self.first(expect_type) if expect_type is not None else self.first()
                                                            ^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/css/query.py", line 247, in first
    raise NoMatches(f"No nodes match {self!r} on {self.node!r}")
textual.css.query.NoMatches: No nodes match <DOMQuery query='SelectOverlay'> on Select(id='class_value')

from textual.

TomJGooding avatar TomJGooding commented on July 30, 2024

Could you provide an MRE or steps to reproduce?

I've just tried rapidly opening and closing the Select using the simple app below and can't reproduce the crash on the latest Textual v0.70.0.

from textual.app import App, ComposeResult
from textual.widgets import Select

LINES = """I must not fear.
Fear is the mind-killer.
Fear is the little-death that brings total obliteration.
I will face my fear.
I will permit it to pass over me and through me.""".splitlines()


class SelectApp(App):

    def compose(self) -> ComposeResult:
        yield Select((line, line) for line in LINES)


if __name__ == "__main__":
    app = SelectApp()
    app.run()

from textual.

arcivanov avatar arcivanov commented on July 30, 2024

It's a tough one, unfortunately. It's a proprietary app with a complex dialog with Select and Input components populated by network calls via workers.

But the structure is as follows and 'o' followed by 'escape' cycling really really fast produced the above two exceptions. Meanwhile TradeDialog was dynamically filling in the various fields via async workers while being dismissed, is my guess.

class TradeDialog(ModalScreen):
    BINDINGS = [("escape", "dismiss", "Cancel")]  
    ...


class DialogApp(App):
    BINDINGS = [("o", "new_order", "New Order")]
    
    ...

    @work(exclusive=True, group="DialogApp.action_new_order")
    async def action_new_order(self) -> None:
        await self.push_screen_wait(TradeDialog())

from textual.

willmcgugan avatar willmcgugan commented on July 30, 2024

I will need an MRE for this. It could very well be a concurrency issue, but without a place to start I couldn't say what the problem is.

When you say stress testing, are you hammering keys or doing anything automated?

from textual.

willmcgugan avatar willmcgugan commented on July 30, 2024

Can you reproduce it with this? Or modify this code until it does reproduce the issue?

from textual.app import App, ComposeResult
from textual.screen import ModalScreen
from textual.widgets import Label, Select, Footer
from textual import work
from textual.binding import Binding


class StressScreen(ModalScreen):
    BINDINGS = [Binding("escape", "dismiss", "dismiss", priority=True)]

    def compose(self) -> ComposeResult:
        yield Label("foo")
        yield Select([("Hello", "hello"), ("World", "world")])
        yield Select([("Foo", "foo"), ("bar", "bar")])
        yield Footer()


class StressApp(App):
    BINDINGS = [Binding("space", "modal", "modal")]

    def compose(self) -> ComposeResult:
        yield Label("BAR")
        # yield Select([("Foo", "foo"), ("bar", "bar")])
        yield Footer()

    @work(exclusive=True)
    async def action_modal(self) -> None:
        await self.push_screen(StressScreen())


if __name__ == "__main__":
    app = StressApp()
    app.run()

from textual.

arcivanov avatar arcivanov commented on July 30, 2024

I will need an MRE for this. It could very well be a concurrency issue, but without a place to start I couldn't say what the problem is.

When you say stress testing, are you hammering keys or doing anything automated?

I understand that you need an MRE and I'm trying to see if I can provide that for you.

Yes, it was just finger testing, I bumped into the problem ensuring responsive population of the dialog which resulted in me bumping into that error.

from textual.

arcivanov avatar arcivanov commented on July 30, 2024

Two more separate failures doing the same thing. These seem all to be focused on there not being a SelectOverlay.

One (also causing a shutdown deadlock):

06-23 11:43:57.316 ERROR    bt.ui [__init__:593]: Unhandled exception occurred
Traceback (most recent call last):
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/message_pump.py", line 542, in _pre_process
    await self._dispatch_message(events.Mount())
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/message_pump.py", line 650, in _dispatch_message
    await self.on_event(message)
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/message_pump.py", line 719, in on_event
    await self._on_message(event)
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/message_pump.py", line 740, in _on_message
    await invoke(method, message)
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/_callback.py", line 85, in invoke
    return await _invoke(callback, *params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/_callback.py", line 45, in _invoke
    result = callback(*params[:parameter_count])
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/widgets/_select.py", line 491, in _on_mount
    self._setup_options_renderables()
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/widgets/_select.py", line 413, in _setup_options_renderables
    option_list = self.query_one(SelectOverlay)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/dom.py", line 1340, in query_one
    return query.only_one() if expect_type is None else query.only_one(expect_type)
           ^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/css/query.py", line 278, in only_one
    self.first(expect_type) if expect_type is not None else self.first()
                                                            ^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/css/query.py", line 247, in first
    raise NoMatches(f"No nodes match {self!r} on {self.node!r}")
textual.css.query.NoMatches: No nodes match <DOMQuery query='SelectOverlay'> on Select(id='class_value')
06-23 11:44:02.769 CRITICAL bt.trader [trade:732]: Critical failures occurred - shutting down
  | ExceptionGroup: Critical failures occurred - shutting down (1 sub-exception)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File "/home/arcivanov/Documents/src/karellen/app/src/main/python/app/trade.py", line 700, in _ui_loop
    |     await trader_app.run_async()
    |   File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/app.py", line 1572, in run_async
    |     await app._shutdown()
    |   File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/app.py", line 2804, in _shutdown
    |     await self._close_all()
    |   File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/app.py", line 2784, in _close_all
    |     await self._prune_node(stack_screen)
    |   File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/app.py", line 3445, in _prune_node
    |     raise asyncio.TimeoutError(
    | TimeoutError: Timeout waiting for [Header(), TradingClockBar(), Grid(id='content-grid'), Footer(), ToastRack(id='textual-toastrack'), Tooltip(id='textual-tooltip')] to close; possible deadlock (consider changing App.CLOSE_TIMEOUT)
    | 
    +------------------------------------

Two:

06-23 12:12:10.164 ERROR    bt.ui [__init__:593]: Unhandled exception occurred
Traceback (most recent call last):
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/message_pump.py", line 542, in _pre_process
    await self._dispatch_message(events.Mount())
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/message_pump.py", line 650, in _dispatch_message
    await self.on_event(message)
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/message_pump.py", line 719, in on_event
    await self._on_message(event)
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/message_pump.py", line 740, in _on_message
    await invoke(method, message)
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/_callback.py", line 85, in invoke
    return await _invoke(callback, *params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/_callback.py", line 45, in _invoke
    result = callback(*params[:parameter_count])
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/widgets/_select.py", line 491, in _on_mount
    self._setup_options_renderables()
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/widgets/_select.py", line 413, in _setup_options_renderables
    option_list = self.query_one(SelectOverlay)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/dom.py", line 1340, in query_one
    return query.only_one() if expect_type is None else query.only_one(expect_type)
           ^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/css/query.py", line 278, in only_one
    self.first(expect_type) if expect_type is not None else self.first()
                                                            ^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/css/query.py", line 247, in first
    raise NoMatches(f"No nodes match {self!r} on {self.node!r}")

from textual.

arcivanov avatar arcivanov commented on July 30, 2024

I've added a debug log (non-reordering) to the MessagePump._dispatch_message as follows:

        with self.prevent(*message._prevent):
            logger.debug(f"{self=!r}, {message=!r}, {message.time=!r}, {message._sender=!r}")

I've grepped the resulting logs as follows.
The curious extract is here. I marked the Select.compose and Select.mount with **** between which you can see SelectOverlay being unmounted by the message from the App, marked with ^^^^.

The @nnnnnn are object IDs to help tracking individual objects.

06-23 16:43:28.494 DEBUG    textual.mp [message_pump:651]: self=TraderApp(title='App', classes={'-dark-mode'})@139731289724768, message=Key(key='o', character='o', name='o', is_printable=True), message.time=20890.646179957, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.494 DEBUG    textual.mp [message_pump:651]: self=TraderApp(title='App', classes={'-dark-mode'})@139731289724768, message=Key(key='escape', character='\x1b', name='escape', is_printable=False, aliases=['escape', 'ctrl+left_square_brace']), message.time=20890.754974334, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.494 DEBUG    textual.mp [message_pump:651]: self=TraderApp(title='App', classes={'-dark-mode'})@139731289724768, message=Key(key='o', character='o', name='o', is_printable=True), message.time=20890.778885442, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.496 DEBUG    textual.mp [message_pump:651]: self=BoundDataTable(id='positions')@139731281503744, message=Key(key='escape', character='\x1b', name='escape', is_printable=False, aliases=['escape', 'ctrl+left_square_brace']), message.time=20890.754974334, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.496 DEBUG    textual.mp [message_pump:651]: self=BoundDataTable(id='positions')@139731281503744, message=Key(key='o', character='o', name='o', is_printable=True), message.time=20890.778885442, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.496 DEBUG    textual.mp [message_pump:651]: self=Screen(id='_default')@139731283636352, message=ScreenSuspend(), message.time=20890.791410347, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.497 DEBUG    textual.mp [message_pump:651]: self=PositionsTabPane(id='tab-positions')@139731292308976, message=Key(key='escape', character='\x1b', name='escape', is_printable=False, aliases=['escape', 'ctrl+left_square_brace']), message.time=20890.754974334, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.497 DEBUG    textual.mp [message_pump:651]: self=PositionsTabPane(id='tab-positions')@139731292308976, message=Key(key='o', character='o', name='o', is_printable=True), message.time=20890.778885442, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.500 DEBUG    textual.mp [message_pump:651]: self=ContentSwitcher()@139731283045232, message=Key(key='escape', character='\x1b', name='escape', is_printable=False, aliases=['escape', 'ctrl+left_square_brace']), message.time=20890.754974334, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.501 DEBUG    textual.mp [message_pump:651]: self=ContentSwitcher()@139731283045232, message=Key(key='o', character='o', name='o', is_printable=True), message.time=20890.778885442, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.504 DEBUG    textual.mp [message_pump:651]: self=TabbedContent(id='tabbed-content')@139731281500480, message=Key(key='escape', character='\x1b', name='escape', is_printable=False, aliases=['escape', 'ctrl+left_square_brace']), message.time=20890.754974334, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.504 DEBUG    textual.mp [message_pump:651]: self=TabbedContent(id='tabbed-content')@139731281500480, message=Key(key='o', character='o', name='o', is_printable=True), message.time=20890.778885442, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.505 DEBUG    textual.mp [message_pump:651]: self=Select(id='class_value')@139731258674736, message=Compose(), message.time=20890.801108438, message._sender=Select(id='class_value')@139731258674736
06-23 16:43:28.512 DEBUG    textual.mp [message_pump:651]: self=Grid(id='content-grid')@139731281499616, message=Key(key='escape', character='\x1b', name='escape', is_printable=False, aliases=['escape', 'ctrl+left_square_brace']), message.time=20890.754974334, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.512 DEBUG    textual.mp [message_pump:651]: self=Grid(id='content-grid')@139731281499616, message=Key(key='o', character='o', name='o', is_printable=True), message.time=20890.778885442, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.513 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730923322640, message=Compose(), message.time=20890.809339622, message._sender=SelectOverlay()@139730923322640
06-23 16:43:28.513 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730923322640, message=Mount(), message.time=20890.809402109, message._sender=SelectOverlay()@139730923322640
06-23 16:43:28.514 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730929245296, message=Compose(), message.time=20890.810261499, message._sender=SelectOverlay()@139730929245296
06-23 16:43:28.514 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730929245296, message=Mount(), message.time=20890.810321291, message._sender=SelectOverlay()@139730929245296
06-23 16:43:28.515 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730929253216, message=Compose(), message.time=20890.811152688, message._sender=SelectOverlay()@139730929253216
06-23 16:43:28.515 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730929253216, message=Mount(), message.time=20890.811217529, message._sender=SelectOverlay()@139730929253216
06-23 16:43:28.516 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730928933568, message=Compose(), message.time=20890.812130669, message._sender=SelectOverlay()@139730928933568
06-23 16:43:28.516 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730928933568, message=Mount(), message.time=20890.812194138, message._sender=SelectOverlay()@139730928933568
06-23 16:43:28.516 DEBUG    textual.mp [message_pump:651]: self=Screen(id='_default')@139731283636352, message=Key(key='escape', character='\x1b', name='escape', is_printable=False, aliases=['escape', 'ctrl+left_square_brace']), message.time=20890.754974334, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.516 DEBUG    textual.mp [message_pump:651]: self=Screen(id='_default')@139731283636352, message=Key(key='o', character='o', name='o', is_printable=True), message.time=20890.778885442, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.527 DEBUG    textual.mp [message_pump:651]: self=Select(id='class_value')@139731258674736, message=Mount(), message.time=20890.823177727, message._sender=Select(id='class_value')@139731258674736
06-23 16:43:28.530 DEBUG    textual.mp [message_pump:651]: self=Select(id='class_value')@139731258674736, message=Changed(Select(id='class_value'), 'equity'), message.time=20890.825941763, message._sender=Select(id='class_value')@139731258674736
06-23 16:43:28.539 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730923322640, message=OptionHighlighted(option_list=SelectOverlay(), option=<textual.widgets._option_list.Option object at 0x7f15a40c76b0>, option_id=None, option_index=1), message.time=20890.825129853, message._sender=SelectOverlay()@139730923322640
06-23 16:43:28.539 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730929245296, message=OptionHighlighted(option_list=SelectOverlay(), option=<textual.widgets._option_list.Option object at 0x7f15a466e150>, option_id=None, option_index=2), message.time=20890.827962497, message._sender=SelectOverlay()@139730929245296
06-23 16:43:28.539 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730929253216, message=OptionHighlighted(option_list=SelectOverlay(), option=<textual.widgets._option_list.Option object at 0x7f15a4411160>, option_id=None, option_index=1), message.time=20890.831229074, message._sender=SelectOverlay()@139730929253216
06-23 16:43:28.539 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730928933568, message=OptionHighlighted(option_list=SelectOverlay(), option=<textual.widgets._option_list.Option object at 0x7f15b1bc3bc0>, option_id=None, option_index=1), message.time=20890.833990736, message._sender=SelectOverlay()@139730928933568
06-23 16:43:28.541 DEBUG    textual.mp [message_pump:651]: self=Grid(id='data-grid')@139731258975120, message=Changed(Select(id='class_value'), 'equity'), message.time=20890.825941763, message._sender=Select(id='class_value')@139731258674736
06-23 16:43:28.542 DEBUG    textual.mp [message_pump:651]: self=TradeWidget()@139731262108400, message=Changed(Select(id='class_value'), 'equity'), message.time=20890.825941763, message._sender=Select(id='class_value')@139731258674736
06-23 16:43:28.548 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730932036144, message=ScreenResume(), message.time=20890.791896767, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.576 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730932036144, message=Update(SelectOverlay()), message.time=20890.809516684, message._sender=SelectOverlay()@139730923322640
06-23 16:43:28.576 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730932036144, message=Update(SelectOverlay()), message.time=20890.810428922, message._sender=SelectOverlay()@139730929245296
06-23 16:43:28.576 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730932036144, message=Update(SelectOverlay()), message.time=20890.811339157, message._sender=SelectOverlay()@139730929253216
06-23 16:43:28.576 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730932036144, message=Update(SelectOverlay()), message.time=20890.812300027, message._sender=SelectOverlay()@139730928933568
06-23 16:43:28.577 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730932036144, message=Update(Select(id='class_value')), message.time=20890.826033725, message._sender=Select(id='class_value')@139731258674736
06-23 16:43:28.577 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730932036144, message=Layout(), message.time=20890.826049194, message._sender=Select(id='class_value')@139731258674736
06-23 16:43:28.577 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730932036144, message=Update(SelectOverlay()), message.time=20890.834999135, message._sender=SelectOverlay()@139730923322640
06-23 16:43:28.577 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730932036144, message=Update(SelectOverlay()), message.time=20890.835157371, message._sender=SelectOverlay()@139730929245296
06-23 16:43:28.577 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730932036144, message=Update(SelectOverlay()), message.time=20890.835281594, message._sender=SelectOverlay()@139730929253216
06-23 16:43:28.577 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730932036144, message=Update(SelectOverlay()), message.time=20890.835398703, message._sender=SelectOverlay()@139730928933568
06-23 16:43:28.583 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730932036144, message=Changed(Select(id='class_value'), 'equity'), message.time=20890.825941763, message._sender=Select(id='class_value')@139731258674736
06-23 16:43:28.616 DEBUG    textual.mp [message_pump:651]: self=Select(id='class_value')@139731258674736, message=Resize(size=Size(width=38, height=3), virtual_size=Size(width=38, height=3)), message.time=20890.849074263, message._sender=TradeDialog()@139730932036144
06-23 16:43:28.616 DEBUG    textual.mp [message_pump:651]: self=Select(id='class_value')@139731258674736, message=Show(), message.time=20890.849266723, message._sender=TradeDialog()@139730932036144
06-23 16:43:28.639 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730932036144, message=Update(Select(id='class_value')), message.time=20890.912467759, message._sender=Select(id='class_value')@139731258674736
06-23 16:43:28.639 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730932036144, message=Layout(), message.time=20890.912470644, message._sender=Select(id='class_value')@139731258674736
06-23 16:43:28.640 DEBUG    textual.mp [message_pump:651]: self=TraderApp(title='App', classes={'-dark-mode'})@139731289724768, message=Key(key='escape', character='\x1b', name='escape', is_printable=False, aliases=['escape', 'ctrl+left_square_brace']), message.time=20890.754974334, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.645 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730932036144, message=ScreenSuspend(), message.time=20890.936814161, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.646 DEBUG    textual.mp [message_pump:651]: self=Screen(id='_default')@139731283636352, message=ScreenResume(), message.time=20890.936869906, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.649 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730923322640, message=Unmount(), message.time=20890.945755346, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.650 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730929245296, message=Unmount(), message.time=20890.946170864, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.650 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730929253216, message=Unmount(), message.time=20890.946530547, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.651 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730928933568, message=Unmount(), message.time=20890.946916921, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.651 DEBUG    textual.mp [message_pump:651]: self=Select(id='class_value')@139731258674736, message=Unmount(), message.time=20890.94712995, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.653 DEBUG    textual.mp [message_pump:651]: self=TraderApp(title='App', classes={'-dark-mode'})@139731289724768, message=Key(key='o', character='o', name='o', is_printable=True), message.time=20890.778885442, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.654 DEBUG    textual.mp [message_pump:651]: self=Screen(id='_default')@139731283636352, message=ScreenSuspend(), message.time=20890.949806422, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.664 DEBUG    textual.mp [message_pump:651]: self=Select(id='class_value')@139730787770656, message=Compose(), message.time=20890.960245431, message._sender=Select(id='class_value')@139730787770656
06-23 16:43:28.675 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730786701520, message=Compose(), message.time=20890.970911665, message._sender=SelectOverlay()@139730786701520
06-23 16:43:28.675 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730786701520, message=Mount(), message.time=20890.971021641, message._sender=SelectOverlay()@139730786701520
06-23 16:43:28.676 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730786709488, message=Compose(), message.time=20890.972492285, message._sender=SelectOverlay()@139730786709488
06-23 16:43:28.676 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730786709488, message=Mount(), message.time=20890.972594286, message._sender=SelectOverlay()@139730786709488
06-23 16:43:28.678 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730929243808, message=Compose(), message.time=20890.974516606, message._sender=SelectOverlay()@139730929243808
06-23 16:43:28.678 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730929243808, message=Mount(), message.time=20890.974591446, message._sender=SelectOverlay()@139730929243808
06-23 16:43:28.680 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730786807456, message=Compose(), message.time=20890.975994714, message._sender=SelectOverlay()@139730786807456
06-23 16:43:28.680 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730786807456, message=Mount(), message.time=20890.976070196, message._sender=SelectOverlay()@139730786807456
06-23 16:43:28.691 DEBUG    textual.mp [message_pump:651]: self=Select(id='class_value')@139730787770656, message=Mount(), message.time=20890.987516952, message._sender=Select(id='class_value')@139730787770656
06-23 16:43:28.694 DEBUG    textual.mp [message_pump:651]: self=Select(id='class_value')@139730787770656, message=Changed(Select(id='class_value'), 'equity'), message.time=20890.990670588, message._sender=Select(id='class_value')@139730787770656
06-23 16:43:28.705 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730786701520, message=OptionHighlighted(option_list=SelectOverlay(), option=<textual.widgets._option_list.Option object at 0x7f159be7cce0>, option_id=None, option_index=1), message.time=20890.989824002, message._sender=SelectOverlay()@139730786701520
06-23 16:43:28.705 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730786709488, message=OptionHighlighted(option_list=SelectOverlay(), option=<textual.widgets._option_list.Option object at 0x7f15b1c9a150>, option_id=None, option_index=2), message.time=20890.992779337, message._sender=SelectOverlay()@139730786709488
06-23 16:43:28.705 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730929243808, message=OptionHighlighted(option_list=SelectOverlay(), option=<textual.widgets._option_list.Option object at 0x7f159be94980>, option_id=None, option_index=1), message.time=20890.995767322, message._sender=SelectOverlay()@139730929243808
06-23 16:43:28.705 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730786807456, message=OptionHighlighted(option_list=SelectOverlay(), option=<textual.widgets._option_list.Option object at 0x7f159be96900>, option_id=None, option_index=1), message.time=20891.000238856, message._sender=SelectOverlay()@139730786807456
06-23 16:43:28.708 DEBUG    textual.mp [message_pump:651]: self=Grid(id='data-grid')@139730786490976, message=Changed(Select(id='class_value'), 'equity'), message.time=20890.990670588, message._sender=Select(id='class_value')@139730787770656
06-23 16:43:28.709 DEBUG    textual.mp [message_pump:651]: self=TradeWidget()@139730787755808, message=Changed(Select(id='class_value'), 'equity'), message.time=20890.990670588, message._sender=Select(id='class_value')@139730787770656
06-23 16:43:28.715 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730923321152, message=ScreenResume(), message.time=20890.95016878, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.743 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730923321152, message=Update(SelectOverlay()), message.time=20890.971202931, message._sender=SelectOverlay()@139730786701520
06-23 16:43:28.743 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730923321152, message=Update(SelectOverlay()), message.time=20890.972787468, message._sender=SelectOverlay()@139730786709488
06-23 16:43:28.743 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730923321152, message=Update(SelectOverlay()), message.time=20890.974790068, message._sender=SelectOverlay()@139730929243808
06-23 16:43:28.743 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730923321152, message=Update(SelectOverlay()), message.time=20890.976201772, message._sender=SelectOverlay()@139730786807456
06-23 16:43:28.744 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730923321152, message=Update(Select(id='class_value')), message.time=20890.990762139, message._sender=Select(id='class_value')@139730787770656
06-23 16:43:28.744 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730923321152, message=Layout(), message.time=20890.990767629, message._sender=Select(id='class_value')@139730787770656
06-23 16:43:28.744 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730923321152, message=Update(SelectOverlay()), message.time=20891.001371867, message._sender=SelectOverlay()@139730786701520
06-23 16:43:28.744 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730923321152, message=Update(SelectOverlay()), message.time=20891.001539521, message._sender=SelectOverlay()@139730786709488
06-23 16:43:28.744 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730923321152, message=Update(SelectOverlay()), message.time=20891.001671679, message._sender=SelectOverlay()@139730929243808
06-23 16:43:28.744 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730923321152, message=Update(SelectOverlay()), message.time=20891.001790461, message._sender=SelectOverlay()@139730786807456
06-23 16:43:28.752 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730923321152, message=Changed(Select(id='class_value'), 'equity'), message.time=20890.990670588, message._sender=Select(id='class_value')@139730787770656
06-23 16:43:28.784 DEBUG    textual.mp [message_pump:651]: self=Select(id='class_value')@139730787770656, message=Resize(size=Size(width=38, height=3), virtual_size=Size(width=38, height=3)), message.time=20891.015806028, message._sender=TradeDialog()@139730923321152
06-23 16:43:28.784 DEBUG    textual.mp [message_pump:651]: self=Select(id='class_value')@139730787770656, message=Show(), message.time=20891.015939638, message._sender=TradeDialog()@139730923321152
06-23 16:43:28.805 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730923321152, message=Update(Select(id='class_value')), message.time=20891.08043632, message._sender=Select(id='class_value')@139730787770656
06-23 16:43:28.805 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730923321152, message=Layout(), message.time=20891.080439095, message._sender=Select(id='class_value')@139730787770656
06-23 16:43:28.806 DEBUG    textual.mp [message_pump:651]: self=TraderApp(title='App', classes={'-dark-mode'})@139731289724768, message=Changed(Select(id='class_value'), 'equity'), message.time=20890.825941763, message._sender=Select(id='class_value')@139731258674736
06-23 16:43:28.807 DEBUG    textual.mp [message_pump:651]: self=TraderApp(title='App', classes={'-dark-mode'})@139731289724768, message=Key(key='o', character='o', name='o', is_printable=True), message.time=20890.921027278, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.807 DEBUG    textual.mp [message_pump:651]: self=TraderApp(title='App', classes={'-dark-mode'})@139731289724768, message=Key(key='escape', character='\x1b', name='escape', is_printable=False, aliases=['escape', 'ctrl+left_square_brace']), message.time=20890.926305472, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.808 DEBUG    textual.mp [message_pump:651]: self=TraderApp(title='App', classes={'-dark-mode'})@139731289724768, message=Changed(Select(id='class_value'), 'equity'), message.time=20890.990670588, message._sender=Select(id='class_value')@139730787770656
06-23 16:43:28.808 DEBUG    textual.mp [message_pump:651]: self=TraderApp(title='App', classes={'-dark-mode'})@139731289724768, message=Key(key='o', character='o', name='o', is_printable=True), message.time=20891.047642878, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.830 DEBUG    textual.mp [message_pump:651]: self=TradeWidget()@139730787755808, message=Key(key='o', character='o', name='o', is_printable=True), message.time=20890.921027278, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.831 DEBUG    textual.mp [message_pump:651]: self=TradeWidget()@139730787755808, message=Key(key='escape', character='\x1b', name='escape', is_printable=False, aliases=['escape', 'ctrl+left_square_brace']), message.time=20890.926305472, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.831 DEBUG    textual.mp [message_pump:651]: self=TradeWidget()@139730787755808, message=Key(key='o', character='o', name='o', is_printable=True), message.time=20891.047642878, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.831 DEBUG    textual.mp [message_pump:651]: self=TraderApp(title='App', classes={'-dark-mode'})@139731289724768, message=Key(key='escape', character='\x1b', name='escape', is_printable=False, aliases=['escape', 'ctrl+left_square_brace']), message.time=20891.09018162, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.831 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730923321152, message=Key(key='o', character='o', name='o', is_printable=True), message.time=20890.921027278, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.831 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730923321152, message=Key(key='escape', character='\x1b', name='escape', is_printable=False, aliases=['escape', 'ctrl+left_square_brace']), message.time=20890.926305472, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.832 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730923321152, message=Key(key='o', character='o', name='o', is_printable=True), message.time=20891.047642878, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.832 DEBUG    textual.mp [message_pump:651]: self=TradeWidget()@139730787755808, message=Key(key='escape', character='\x1b', name='escape', is_printable=False, aliases=['escape', 'ctrl+left_square_brace']), message.time=20891.09018162, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.832 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730923321152, message=Key(key='escape', character='\x1b', name='escape', is_printable=False, aliases=['escape', 'ctrl+left_square_brace']), message.time=20891.09018162, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.834 DEBUG    textual.mp [message_pump:651]: self=TraderApp(title='App', classes={'-dark-mode'})@139731289724768, message=Key(key='o', character='o', name='o', is_printable=True), message.time=20890.921027278, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.834 DEBUG    textual.mp [message_pump:651]: self=TraderApp(title='App', classes={'-dark-mode'})@139731289724768, message=Key(key='escape', character='\x1b', name='escape', is_printable=False, aliases=['escape', 'ctrl+left_square_brace']), message.time=20890.926305472, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.838 DEBUG    textual.mp [message_pump:651]: self=Screen(id='_default')@139731283636352, message=ScreenResume(), message.time=20891.130593378, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.843 DEBUG    textual.mp [message_pump:651]: self=TradeDialog()@139730923321152, message=ScreenSuspend(), message.time=20891.130505994, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.845 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730786701520, message=Unmount(), message.time=20891.141029221, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.845 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730786709488, message=Unmount(), message.time=20891.141415444, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.845 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730929243808, message=Unmount(), message.time=20891.141799213, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.846 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730786807456, message=Unmount(), message.time=20891.142143948, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.846 DEBUG    textual.mp [message_pump:651]: self=Select(id='class_value')@139730787770656, message=Unmount(), message.time=20891.142328985, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.849 DEBUG    textual.mp [message_pump:651]: self=TraderApp(title='App', classes={'-dark-mode'})@139731289724768, message=Key(key='o', character='o', name='o', is_printable=True), message.time=20891.047642878, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.850 DEBUG    textual.mp [message_pump:651]: self=Screen(id='_default')@139731283636352, message=ScreenSuspend(), message.time=20891.145974843, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
**** 06-23 16:43:28.859 DEBUG    textual.mp [message_pump:651]: self=Select(id='class_value')@139731151420544, message=Compose(), message.time=20891.155474172, message._sender=Select(id='class_value')@139731151420544
06-23 16:43:28.867 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730783297632, message=Compose(), message.time=20891.16355812, message._sender=SelectOverlay()@139730783297632
06-23 16:43:28.867 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730783297632, message=Mount(), message.time=20891.163623453, message._sender=SelectOverlay()@139730783297632
06-23 16:43:28.869 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730783305648, message=Compose(), message.time=20891.165732833, message._sender=SelectOverlay()@139730783305648
06-23 16:43:28.869 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730783305648, message=Mount(), message.time=20891.165807383, message._sender=SelectOverlay()@139730783305648
06-23 16:43:28.870 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730783756096, message=Compose(), message.time=20891.166716325, message._sender=SelectOverlay()@139730783756096
06-23 16:43:28.870 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730783756096, message=Mount(), message.time=20891.166784813, message._sender=SelectOverlay()@139730783756096
06-23 16:43:28.871 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730783764064, message=Compose(), message.time=20891.167669299, message._sender=SelectOverlay()@139730783764064
06-23 16:43:28.871 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730783764064, message=Mount(), message.time=20891.167736375, message._sender=SelectOverlay()@139730783764064
06-23 16:43:28.873 DEBUG    textual.mp [message_pump:651]: self=TraderApp(title='App', classes={'-dark-mode'})@139731289724768, message=Key(key='escape', character='\x1b', name='escape', is_printable=False, aliases=['escape', 'ctrl+left_square_brace']), message.time=20891.09018162, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
06-23 16:43:28.878 DEBUG    textual.mp [message_pump:651]: self=Screen(id='_default')@139731283636352, message=ScreenResume(), message.time=20891.169215194, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
^^^^ 06-23 16:43:28.892 DEBUG    textual.mp [message_pump:651]: self=SelectOverlay()@139730783297632, message=Unmount(), message.time=20891.18787608, message._sender=TraderApp(title='App', classes={'-dark-mode'})@139731289724768
**** 06-23 16:43:28.892 DEBUG    textual.mp [message_pump:651]: self=Select(id='class_value')@139731151420544, message=Mount(), message.time=20891.188456246, message._sender=Select(id='class_value')@139731151420544

!!!!!!!!!!
06-23 16:43:28.892 ERROR    bt.ui [__init__:594]: Unhandled exception occurred
textual.css.query.NoMatches: No nodes match <DOMQuery query='SelectOverlay'> on Select(id='class_value')
!!!!!!!!!!

selectoverlay_unmount.log

from textual.

TomJGooding avatar TomJGooding commented on July 30, 2024

Please stop just spamming tracebacks and logs from your proprietary app.

You say that you understand that this needs an MRE, but I'll try explaining this more bluntly. Without any way to reproduce the issue you're describing, this is impossible to debug. You can't expect anyone to understand exactly what your code is doing only based on the tracebacks or logs.

I had no idea this even involved a ModalScreen from your original issue post.

from textual.

arcivanov avatar arcivanov commented on July 30, 2024

I have the MRE. The culprit is the number of controls being composed. With just 2 controls the crash is not reproducible. As the number of composed controls grows (i.e. range(20)) the problems becomes increasingly easy to reproduce.
The core failure is unfortunately obscured by a shutdown deadlock so a logging system has to be used to capture the failure (I overwrote the _handle_exception to log it).

import logging
from typing import Type

from textual import work
from textual._path import CSSPathType
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.driver import Driver
from textual.screen import ModalScreen
from textual.widgets import Label, Select, Footer

logger = logging.getLogger("bt.stress")


class StressScreen(ModalScreen):
    BINDINGS = [Binding("escape", "dismiss", "dismiss")]

    def __init__(self, data, name: str | None = None, id: str | None = None, classes: str | None = None) -> None:
        super().__init__(name, id, classes)
        self.data = dict(data)

    def compose(self) -> ComposeResult:
        yield Select([("Mode A", "A"), ("Mode B", "B")], id="mode_value",
                     value=self.data.get("mode", Select.BLANK))
        if self.data.get("mode") == "B":
            yield Select([("Foo", "foo"), ("bar", "bar")], id="dependent_value",
                         value=self.data.get("dependent", Select.BLANK))

        for idx in range(20):
            yield Select([(f"Foo{idx}", f"foo{idx}"), (f"Bar{idx}", f"bar{idx}")],
                         id=f"independent{idx}_value",
                         value=self.data.get(f"independendent{idx}", Select.BLANK))

    async def on_select_changed(self, message: Select.Changed) -> None:
        option_name = message.select.id[:-6]
        if message.select.id == "mode_value":
            if message.value != self.data.get(option_name):
                self.data[option_name] = message.value
                self._recompose()
                return

        self.data[option_name] = message.value

    @work(exclusive=True, group="StressScreen.recompose")
    async def _recompose(self):
        await self.recompose()


class StressApp(App):
    BINDINGS = [Binding("o", "modal", "modal")]

    def __init__(self, driver_class: Type[Driver] | None = None, css_path: CSSPathType | None = None,
                 watch_css: bool = False):
        super().__init__(driver_class, css_path, watch_css)
        self.data = {"mode": "B", "dependent": "bar"}

    def compose(self) -> ComposeResult:
        yield Label("BAR")
        yield Footer()

    @work(exclusive=True)
    async def action_modal(self) -> None:
        await self.push_screen_wait(StressScreen(self.data))

    def _handle_exception(self, error: Exception) -> None:
        logger.exception("Unhandled exception occurred", exc_info=error)
        super()._handle_exception(error)

Three to five rapid pairs of o-ESC produce the below:

06-23 21:33:00.720 ERROR    bt.stress [stress:67]: Unhandled exception occurred
Traceback (most recent call last):
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/message_pump.py", line 540, in _pre_process
    await self._dispatch_message(events.Compose())
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/message_pump.py", line 655, in _dispatch_message
    await self.on_event(message)
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/message_pump.py", line 724, in on_event
    await self._on_message(event)
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/message_pump.py", line 745, in _on_message
    await invoke(method, message)
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/_callback.py", line 85, in invoke
    return await _invoke(callback, *params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/_callback.py", line 47, in _invoke
    result = await result
             ^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/widget.py", line 3720, in _on_compose
    await self._compose()
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/widget.py", line 3734, in _compose
    await self.mount_composed_widgets(widgets)
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/widget.py", line 3747, in mount_composed_widgets
    await self.mount_all(widgets)
          ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/widget.py", line 1016, in mount_all
    await_mount = self.mount(*widgets, before=before, after=after)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/widget.py", line 947, in mount
    raise MountError(f"Can't mount widget(s) before {self!r} is mounted")
textual.widget.MountError: Can't mount widget(s) before StressScreen() is mounted

followed by

06-23 21:33:06.368 CRITICAL bt.trader [trade:734]: Critical failures occurred - shutting down
  | ExceptionGroup: Critical failures occurred - shutting down (1 sub-exception)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File "/home/arcivanov/Documents/src/karellen/app/src/main/python/app/trade.py", line 702, in _ui_loop
    |     await stress_app.run_async()
    |   File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/app.py", line 1572, in run_async
    |     await app._shutdown()
    |   File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/app.py", line 2804, in _shutdown
    |     await self._close_all()
    |   File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/app.py", line 2784, in _close_all
    |     await self._prune_node(stack_screen)
    |   File "/home/arcivanov/.pyenv/versions/app/lib/python3.12/site-packages/textual/app.py", line 3445, in _prune_node
    |     raise asyncio.TimeoutError(
    | TimeoutError: Timeout waiting for [Label(), Footer(), ToastRack(id='textual-toastrack'), Tooltip(id='textual-tooltip')] to close; possible deadlock (consider changing App.CLOSE_TIMEOUT)
    | 
    +------------------------------------

Log is attached.
stress-log-2024-06-23T21:32:53.825.log

from textual.

github-actions avatar github-actions commented on July 30, 2024

Don't forget to star the repository!

Follow @textualizeio for Textual updates.

from textual.

arcivanov avatar arcivanov commented on July 30, 2024

I've installed the main branch and the problem still persists @willmcgugan

from textual.

willmcgugan avatar willmcgugan commented on July 30, 2024

With what? Your last MRE?

I can no longer reproduce it in main.

Could you paste the output of the following command:

textual diagnose

from textual.

arcivanov avatar arcivanov commented on July 30, 2024

So there were two crashes in this bug:

textual.widget.MountError: Can't mount widget(s) before StressScreen() is mounted

and

textual.css.query.NoMatches: No nodes match <DOMQuery query='SelectOverlay'> on Select(id='class_value')

The MRE reproduced the first one, but I never got to the second one because the MountError was blocking.

Now the second one is still there even though the MRE doesn't crash anymore.

Let me see if I can modify the MRE to reproduce the second one.

from textual.

arcivanov avatar arcivanov commented on July 30, 2024

Actually just using MRE I just triggered a whole bunch of other issues as well:

Textual Diagnostics

Versions

Name Value
Textual 0.70.0
Rich 13.7.1

Python

Name Value
Version 3.12.3
Implementation CPython
Compiler GCC 13.2.1 20240316 (Red Hat 13.2.1-7)
Executable /home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/bin/python

Operating System

Name Value
System Linux
Release 6.9.5-200.fc40.x86_64
Version #1 SMP PREEMPT_DYNAMIC Sun Jun 16 15:47:09 UTC 2024

Terminal

Name Value
Terminal Application Unknown
TERM xterm-256color
COLORTERM truecolor
FORCE_COLOR Not set
NO_COLOR Not set

Rich Console options

Name Value
size width=184, height=52
legacy_windows False
min_width 1
max_width 184
is_terminal True
encoding utf-8
max_height 52
justify None
overflow None
no_wrap False
highlight None
markup None
height None

Just o-ESC of the stress.py just gave me this:

╭─────────────────────────────────────────────────────────────────────────────────────────────────────────── Traceback (most recent call last) ─────────────────────────────────────────
│ /home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/widget.py:3734 in _compose                                                             
│                                                                                                                                                                                       
│   3731 │   │   │   self.app._handle_exception(error)                                            ╭────────────────────── locals ──────────────────────╮                                
│   3732 │   │   else:                                                                            │    self = SelectCurrent()                          │                                
│   3733 │   │   │   self._extend_compose(widgets)                                                │ widgets = [Static(id='label'), Static(), Static()] │                                
│ ❱ 3734 │   │   │   await self.mount_composed_widgets(widgets)                                   ╰────────────────────────────────────────────────────╯                                
│   3735 │                                                                                                                                                                              
│   3736 │   async def mount_composed_widgets(self, widgets: list[Widget]) -> None:                                                                                                     
│   3737 │   │   """Called by Textual to mount widgets after compose.                                                                                                                   
│                                                                                                                                                                                       
│ /home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/widget.py:3747 in mount_composed_widgets                                               
│                                                                                                                                                                                       
│   3744 │   │   │   widgets: A list of child widgets.                                            ╭────────────────────── locals ──────────────────────╮                                
│   3745 │   │   """                                                                              │    self = SelectCurrent()                          │                                
│   3746 │   │   if widgets:                                                                      │ widgets = [Static(id='label'), Static(), Static()] │                                
│ ❱ 3747 │   │   │   await self.mount_all(widgets)                                                ╰────────────────────────────────────────────────────╯                                
│   3748 │                                                                                                                                                                              
│   3749 │   def _extend_compose(self, widgets: list[Widget]) -> None:                                                                                                                  
│   3750 │   │   """Hook to extend composed widgets.                                                                                                                                    
│                                                                                                                                                                                       
│ /home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/widget.py:1016 in mount_all                                                            
│                                                                                                                                                                                       
│   1013 │   │   """                                                                              ╭────────────────────── locals ──────────────────────╮                                
│   1014 │   │   if self.app._exit:                                                               │   after = None                                     │                                
│   1015 │   │   │   return AwaitMount(self, [])                                                  │  before = None                                     │                                
│ ❱ 1016 │   │   await_mount = self.mount(*widgets, before=before, after=after)                   │    self = SelectCurrent()                          │                                
│   1017 │   │   return await_mount                                                               │ widgets = [Static(id='label'), Static(), Static()] │                                
│   1018 │                                                                                        ╰────────────────────────────────────────────────────╯                                
│   1019 │   if TYPE_CHECKING:                                                                                                                                                          
│                                                                                                                                                                                       
│ /home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/widget.py:947 in mount                                                                 
│                                                                                                                                                                                       
│    944 │   │   if self._closing:                                                                ╭────────────────────── locals ──────────────────────╮                                
│    945 │   │   │   return AwaitMount(self, [])                                                  │   after = None                                     │                                
│    946 │   │   if not self.is_attached:                                                         │  before = None                                     │                                
│ ❱  947 │   │   │   raise MountError(f"Can't mount widget(s) before {self!r} is mounted")        │    self = SelectCurrent()                          │                                
│    948 │   │   # Check for duplicate IDs in the incoming widgets                                │ widgets = (Static(id='label'), Static(), Static()) │                                
│    949 │   │   ids_to_mount = [                                                                 ╰────────────────────────────────────────────────────╯                                
│    950 │   │   │   widget_id for widget in widgets if (widget_id := widget.id) is not None                                                                                            
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
MountError: Can't mount widget(s) before SelectCurrent() is mounted

NOTE: 1 of 80 errors shown. Run with textual run --dev to see all errors.
Traceback (most recent call last):
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/message_pump.py", line 540, in _pre_process
    await self._dispatch_message(events.Compose())
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/message_pump.py", line 655, in _dispatch_message
    await self.on_event(message)
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/message_pump.py", line 724, in on_event
    await self._on_message(event)
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/message_pump.py", line 745, in _on_message
    await invoke(method, message)
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/_callback.py", line 85, in invoke
    return await _invoke(callback, *params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/_callback.py", line 47, in _invoke
    result = await result
             ^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/widget.py", line 3720, in _on_compose
    await self._compose()
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/widget.py", line 3734, in _compose
    await self.mount_composed_widgets(widgets)
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/widget.py", line 3747, in mount_composed_widgets
    await self.mount_all(widgets)
          ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/widget.py", line 1016, in mount_all
    await_mount = self.mount(*widgets, before=before, after=after)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/widget.py", line 947, in mount
    raise MountError(f"Can't mount widget(s) before {self!r} is mounted")
textual.widget.MountError: Can't mount widget(s) before Select(id='independent39_value') is mounted

from textual.

arcivanov avatar arcivanov commented on July 30, 2024

Nope, the original problem is still there:

$ textual run --dev stress.py 
Traceback (most recent call last):
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/message_pump.py", line 540, in _pre_process
    await self._dispatch_message(events.Compose())
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/message_pump.py", line 655, in _dispatch_message
    await self.on_event(message)
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/message_pump.py", line 724, in on_event
    await self._on_message(event)
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/message_pump.py", line 745, in _on_message
    await invoke(method, message)
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/_callback.py", line 81, in invoke
    return await _invoke(callback, *params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/_callback.py", line 47, in _invoke
    result = await result
             ^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/widget.py", line 3720, in _on_compose
    await self._compose()
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/widget.py", line 3734, in _compose
    await self.mount_composed_widgets(widgets)
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/widget.py", line 3747, in mount_composed_widgets
    await self.mount_all(widgets)
          ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/widget.py", line 1016, in mount_all
    await_mount = self.mount(*widgets, before=before, after=after)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/widget.py", line 947, in mount
    raise MountError(f"Can't mount widget(s) before {self!r} is mounted")
textual.widget.MountError: Can't mount widget(s) before StressScreen() is mounted
Traceback (most recent call last):
  File "/home/arcivanov/Documents/src/karellen/boris-trading/src/main/python/stress.py", line 41, in <module>
    StressApp().run()
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/app.py", line 1620, in run
    asyncio.run(run_app())
  File "/home/arcivanov/.pyenv/versions/3.12.3/lib/python3.12/asyncio/runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/3.12.3/lib/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/3.12.3/lib/python3.12/asyncio/base_events.py", line 687, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/app.py", line 1606, in run_app
    await self.run_async(
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/app.py", line 1572, in run_async
    await app._shutdown()
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/app.py", line 2804, in _shutdown
    await self._close_all()
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/app.py", line 2784, in _close_all
    await self._prune_node(stack_screen)
  File "/home/arcivanov/.pyenv/versions/3.12.3/envs/boris-trading/lib/python3.12/site-packages/textual/app.py", line 3445, in _prune_node
    raise asyncio.TimeoutError(
TimeoutError: Timeout waiting for [Label(), Footer(), ToastRack(id='textual-toastrack'), Tooltip(id='textual-tooltip')] to close; possible deadlock (consider changing App.CLOSE_TIMEOUT)

from textual.

arcivanov avatar arcivanov commented on July 30, 2024

I've updated using pip install -U git+https://github.com/Textualize/textual

from textual.

arcivanov avatar arcivanov commented on July 30, 2024

Curiously, I'm unable to trigger the case via a pilot, but fairly reliably can trigger it with my fingers. Also, headed pilot seems rather slow in comparison to what I can do by hand.

import asyncio
import sys

from textual import work
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.screen import ModalScreen
from textual.widgets import Label, Select, Footer

exc: Exception | None = None


class StressScreen(ModalScreen):
    BINDINGS = [Binding("escape", "dismiss", "dismiss")]

    def compose(self) -> ComposeResult:
        for idx in range(40):
            yield Select([(f"Foo{idx}", f"foo{idx}"), (f"Bar{idx}", f"bar{idx}")],
                         id=f"independent{idx}_value",
                         value=Select.BLANK)


class StressApp(App):
    BINDINGS = [Binding("o", "modal", "modal")]

    def compose(self) -> ComposeResult:
        yield Label("BAR")
        yield Footer()

    @work(exclusive=True)
    async def action_modal(self) -> None:
        await self.push_screen_wait(StressScreen())

    def _handle_exception(self, error: Exception) -> None:
        global exc
        exc = error
        super()._handle_exception(error)


if __name__ == "__main__":

    async def test():
        app = StressApp()
        try:
            async with app.run_test(headless=False, size=None) as pilot:
                while True:
                    await pilot.press("o")
                    await pilot.press("escape")
                    await asyncio.sleep(0)
        finally:
            if exc:
                sys.excepthook(type(exc), exc, None)


    asyncio.run(test())

from textual.

willmcgugan avatar willmcgugan commented on July 30, 2024

The pilot waits for messages to be processed, unlike manual keypresses.

From your recent traceback I can tell you are not running the latest Textual. Please check you have installed Textual main in to the same venv.

from textual.

arcivanov avatar arcivanov commented on July 30, 2024

Ok I will recheck. What's the standard way of installing the "latest" textual if not via pip install -U git+https://github.com/Textualize/textual ?

from textual.

willmcgugan avatar willmcgugan commented on July 30, 2024

That should work if you have the venv activated. You could also clone it and run pip install -e .

If pip is installed globally, you can use this to ensure it uses the same env as the python command.

python -m pip 

from textual.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.