Closed arcivanov closed 1 week ago
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')
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()
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())
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?
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()
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.
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}")
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')
!!!!!!!!!!
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.
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
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)
Don't forget to star the repository!
Follow @textualizeio for Textual updates.
I've installed the main branch and the problem still persists @willmcgugan
With what? Your last MRE?
I can no longer reproduce it in main.
Could you paste the output of the following command:
textual diagnose
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.
Actually just using MRE I just triggered a whole bunch of other issues as well:
Name | Value |
---|---|
Textual | 0.70.0 |
Rich | 13.7.1 |
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 |
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 |
Name | Value |
---|---|
Terminal Application | Unknown |
TERM | xterm-256color |
COLORTERM | truecolor |
FORCE_COLOR | Not set |
NO_COLOR | Not set |
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
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)
I've updated using pip install -U git+https://github.com/Textualize/textual
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())
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.
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
?
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
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 https://github.com/Textualize/textual/issues/4668#issuecomment-2186463224 when mashing the o and Esc keys.
Yep, I removed the textual completely, installed it from the tree and can no longer bang out any of the problem.
I've been stress-testing my app and I think I found a concurrency issue in the library: