Comments (10)
Sorry, yep, I can confirm it's fixed. And we've switched to TEXTUAL_PRESS
as well, so hopefully our tests are more robust in the future.
from textual.
And then running the tree reporter on that capture file in a subprocess:
You may need to execute this command a couple of times until it reproduces (it looks like some kind of race).
from textual.
Ah! It's now reproducing for me on Linux. I swear it wasn't before, but thank heavens for small favors. Previously I tried reproducing it with an existing virtualenv, but it reproduced after creating a new one. pip list
shows:
Package Version
----------------------- -------
asttokens 2.4.1
coverage 7.5.3
Cython 3.0.10
decorator 5.1.1
executing 2.0.1
greenlet 3.0.3
iniconfig 2.0.0
ipython 8.25.0
jedi 0.19.1
Jinja2 3.1.4
linkify-it-py 2.0.3
markdown-it-py 3.0.0
MarkupSafe 2.1.5
matplotlib-inline 0.1.7
mdit-py-plugins 0.4.1
mdurl 0.1.2
memray 1.12.0
packaging 24.0
parso 0.8.4
pexpect 4.9.0
pip 24.0
pkgconfig 1.5.5
pluggy 1.5.0
prompt_toolkit 3.0.46
ptyprocess 0.7.0
pure-eval 0.2.2
Pygments 2.18.0
pytest 8.2.2
pytest-cov 5.0.0
pytest-textual-snapshot 0.4.0
rich 13.7.1
setuptools 65.5.0
six 1.16.0
stack-data 0.6.3
syrupy 4.6.1
textual 0.67.1
traitlets 5.14.3
typing_extensions 4.12.1
uc-micro-py 1.0.3
wcwidth 0.2.13
from textual.
Adding some debugging to the rlock acquires:
diff --git a/src/textual/rlock.py b/src/textual/rlock.py
index d7a6af2d5..e1acce7fc 100644
--- a/src/textual/rlock.py
+++ b/src/textual/rlock.py
@@ -1,6 +1,10 @@
from __future__ import annotations
from asyncio import Lock, Task, current_task
+import datetime
+import traceback
+
+textual_log = open("/tmp/textual.log", "w")
class RLock:
@@ -16,9 +20,12 @@ class RLock:
task = current_task()
assert task is not None
if self._owner is None or self._owner is not task:
+ print(f"\n{datetime.datetime.now().isoformat()} Acquiring {self} at:", file=textual_log)
+ traceback.print_stack(limit=5, file=textual_log)
await self._lock.acquire()
self._owner = task
self._count += 1
+ print(f"\n{datetime.datetime.now().isoformat()} Lock {self} acquired with count={self._count}", file=textual_log)
def release(self) -> None:
"""Release a previously acquired lock."""
@@ -28,6 +35,7 @@ class RLock:
if self._count < 0:
# Should not occur if every acquire as a release
raise RuntimeError("RLock.release called too many times")
+ print(f"\n{datetime.datetime.now().isoformat()} Lock {self} released, new count={self._count}", file=textual_log)
if self._owner is task:
if not self._count:
self._owner = None
and filtering out the locks that were acquired and then successfully released shows these 3 interesting events, the last one happening immediately before a hang of App.CLOSE_TIMEOUT
seconds:
2024-06-12T14:27:53.508720 Acquiring <textual.rlock.RLock object at 0x7f6469f4b290> at:
File "/tmp/memray-issue/lib/python3.11/site-packages/textual/app.py", line 1572, in run_async
await app._shutdown()
File "/tmp/memray-issue/lib/python3.11/site-packages/textual/app.py", line 2788, in _shutdown
await self._close_all()
File "/tmp/memray-issue/lib/python3.11/site-packages/textual/app.py", line 2763, in _close_all
async with self._dom_lock:
File "/tmp/memray-issue/lib/python3.11/site-packages/textual/rlock.py", line 51, in __aenter__
await self.acquire()
File "/tmp/memray-issue/lib/python3.11/site-packages/textual/rlock.py", line 24, in acquire
traceback.print_stack(limit=5, file=textual_log)
2024-06-12T14:27:53.509372 Lock <textual.rlock.RLock object at 0x7f6469f4b290> acquired with count=1
2024-06-12T14:27:53.509662 Acquiring <textual.rlock.RLock object at 0x7f6468d92050> at:
File "/tmp/memray-issue/lib/python3.11/site-packages/textual/widget.py", line 1130, in recompose
async with self.batch():
File "/opt/bb/lib/python3.11/contextlib.py", line 210, in __aenter__
return await anext(self.gen)
File "/tmp/memray-issue/lib/python3.11/site-packages/textual/widget.py", line 3487, in batch
async with self.lock:
File "/tmp/memray-issue/lib/python3.11/site-packages/textual/rlock.py", line 51, in __aenter__
await self.acquire()
File "/tmp/memray-issue/lib/python3.11/site-packages/textual/rlock.py", line 24, in acquire
traceback.print_stack(limit=5, file=textual_log)
2024-06-12T14:27:53.510188 Lock <textual.rlock.RLock object at 0x7f6468d92050> acquired with count=1
2024-06-12T14:27:53.514009 Acquiring <textual.rlock.RLock object at 0x7f6469f4b290> at:
File "/opt/bb/lib/python3.11/asyncio/events.py", line 84, in _run
self._context.run(self._callback, *self._args)
File "/tmp/memray-issue/lib/python3.11/site-packages/textual/app.py", line 3356, in prune_widgets_task
await self._prune_nodes(widgets)
File "/tmp/memray-issue/lib/python3.11/site-packages/textual/app.py", line 3383, in _prune_nodes
async with self._dom_lock:
File "/tmp/memray-issue/lib/python3.11/site-packages/textual/rlock.py", line 51, in __aenter__
await self.acquire()
File "/tmp/memray-issue/lib/python3.11/site-packages/textual/rlock.py", line 24, in acquire
traceback.print_stack(limit=5, file=textual_log)
So:
App._close_all
acquiresself._dom_lock
Widget.recompose
(for theFooter
widget) acquiresself.lock
(a different lock), but never releases itApp._prune_nodes
tries to acquireself._dom_lock
and blocks, because the task callingApp._close_all
never released it
from textual.
OK, here's a minimal reproducer for the issue:
Create a crash_reproducer.py
with these contents:
from textual.app import App
from textual.binding import Binding
from textual.widgets import Footer
class MyApp(App[None]):
BINDINGS = [
Binding(key="q", action="quit", description="Quit the app"),
]
def compose(self):
yield Footer()
app = MyApp()
app.run()
And then run it with a "q" passed to stdin:
echo q | python crash_reproducer.py
from textual.
Thanks for doing the legwork on this. Looking at it now.
from textual.
In the meantime, an alternative to piping stdin is to use the TEXTUAL_PRESS
env var to simulate keys.
TEXTUAL_PRESS=q python crash_reproducer.py
This is probably better in a test environment, because when stdin is not a tty it can subtly change how the app behaves.
from textual.
Please try Textual 0.68.0
from textual.
Closing, assumed fix.
from textual.
Don't forget to star the repository!
Follow @textualizeio for Textual updates.
from textual.
Related Issues (20)
- Serving files
- Documentation errors on CSS Type Selectors HOT 6
- Updating a label with text containing square brackets hides those brackets and the word behind it HOT 4
- v0.75.0 has massive slowdown HOT 5
- ProgressBar: decrease CPU consumption by adjusting Bar.auto_refresh when styles.display is None HOT 3
- Upgrade Textual's tree-sitter version to 0.22.x HOT 7
- `TextArea` scrollbar position is not updated after paste HOT 2
- Command palette glitch HOT 5
- Command palette cancel HOT 3
- Change command palette key HOT 5
- Watch Highlander for the first time HOT 5
- Make tooltip timer confugrable HOT 2
- Pytest gets stuck when testing app with dynamically mounting animated widgets on timer HOT 5
- v0.76.0 breaks buttons with emojis HOT 7
- self test problems HOT 6
- Investigate flaky `test_command_palette_dismiss_escape_no_results` snapshot test HOT 3
- Inline mode doesn't show CSS errors HOT 1
- Extraneous padding when using inline mode HOT 7
- InvalidStateError when dismissing Screen HOT 5
- Mouse enter in a child widget triggers Leave event in parent HOT 4
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from textual.