Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calling a thread worker from within a thread worker results in a crash #3472

Closed
davep opened this issue Oct 6, 2023 · 1 comment · Fixed by #3586
Closed

Calling a thread worker from within a thread worker results in a crash #3472

davep opened this issue Oct 6, 2023 · 1 comment · Fixed by #3586
Assignees
Labels
bug Something isn't working enhancement New feature or request Task

Comments

@davep
Copy link
Contributor

davep commented Oct 6, 2023

Coming from this question, this reply, and this followup on Discord...

It seems that if you call a thread worker from within a thread worker it results in a crash. This code:

from time import sleep

from textual import on, work
from textual.app import App, ComposeResult
from textual.widgets import Button, Log
from textual.worker import get_current_worker

class WorkerInWorkerApp(App[None]):

    def compose(self) -> ComposeResult:
        yield Button("Start")
        yield Log()

    @work(thread=True)
    def outer(self) -> None:
        worker = get_current_worker()
        for n in range(50):
            if worker.is_cancelled:
                break
            if n == 10:
                self.inner()
            self.call_from_thread(
                self.query_one(Log).write_line,
                f"Outer {n}"
            )
            sleep(0.25)

    @work(thread=True)
    def inner(self) -> None:
        worker = get_current_worker()
        for n in range(50):
            if worker.is_cancelled:
                break
            self.call_from_thread(
                self.query_one(Log).write_line,
                f"Inner {n}"
            )
            sleep(0.25)

    @on(Button.Pressed)
    def start(self) -> None:
        self.outer()

if __name__ == "__main__":
    WorkerInWorkerApp().run()

when run results in this exception:

Task exception was never retrieved
future: <Task finished name='Task-29' coro=<Worker._run() done, defined at /Users/davep/develop/python/textual-sandbox/.venv/lib/python3.10/site-packages/textual/worker.py:351> exception=TypeError("'<' not supported between instances of 'WorkerState' and 'WorkerState'")>
Traceback (most recent call last):
  File "/Users/davep/develop/python/textual-sandbox/.venv/lib/python3.10/site-packages/textual/worker.py", line 363, in _run
    self._result = await self.run()
  File "/Users/davep/develop/python/textual-sandbox/.venv/lib/python3.10/site-packages/textual/worker.py", line 347, in run
    return await (
  File "/Users/davep/develop/python/textual-sandbox/.venv/lib/python3.10/site-packages/textual/worker.py", line 317, in _run_threaded
    return await asyncio.get_running_loop().run_in_executor(
  File "/Users/davep/.pyenv/versions/3.10.10/lib/python3.10/concurrent/futures/thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/Users/davep/develop/python/textual-sandbox/.venv/lib/python3.10/site-packages/textual/worker.py", line 302, in run_callable
    return work()
  File "/Users/davep/develop/python/textual-sandbox/work_from_work.py", line 21, in outer
    self.inner()
  File "/Users/davep/develop/python/textual-sandbox/.venv/lib/python3.10/site-packages/textual/_work_decorator.py", line 144, in decorated
    self.run_worker(
  File "/Users/davep/develop/python/textual-sandbox/.venv/lib/python3.10/site-packages/textual/dom.py", line 270, in run_worker
    worker: Worker[ResultType] = self.workers._new_worker(
  File "/Users/davep/develop/python/textual-sandbox/.venv/lib/python3.10/site-packages/textual/_worker_manager.py", line 119, in _new_worker
    self.add_worker(worker, start=start, exclusive=exclusive)
  File "/Users/davep/develop/python/textual-sandbox/.venv/lib/python3.10/site-packages/textual/_worker_manager.py", line 80, in add_worker
    worker._start(self._app, self._remove_worker)
  File "/Users/davep/develop/python/textual-sandbox/.venv/lib/python3.10/site-packages/textual/worker.py", line 391, in _start
    self._task = asyncio.create_task(self._run(app))
  File "/Users/davep/.pyenv/versions/3.10.10/lib/python3.10/asyncio/tasks.py", line 336, in create_task
    loop = events.get_running_loop()
RuntimeError: no running event loop

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/davep/develop/python/textual-sandbox/.venv/lib/python3.10/site-packages/textual/worker.py", line 374, in _run
    app._fatal_error()
  File "/Users/davep/develop/python/textual-sandbox/.venv/lib/python3.10/site-packages/textual/app.py", line 2000, in _fatal_error
    traceback = Traceback(
  File "/Users/davep/develop/python/textual-sandbox/.venv/lib/python3.10/site-packages/rich/traceback.py", line 264, in __init__
    trace = self.extract(
  File "/Users/davep/develop/python/textual-sandbox/.venv/lib/python3.10/site-packages/rich/traceback.py", line 449, in extract
    locals={
  File "/Users/davep/develop/python/textual-sandbox/.venv/lib/python3.10/site-packages/rich/traceback.py", line 450, in <dictcomp>
    key: pretty.traverse(
  File "/Users/davep/develop/python/textual-sandbox/.venv/lib/python3.10/site-packages/rich/pretty.py", line 853, in traverse
    node = _traverse(_object, root=True)
  File "/Users/davep/develop/python/textual-sandbox/.venv/lib/python3.10/site-packages/rich/pretty.py", line 648, in _traverse
    args = list(iter_rich_args(rich_repr_result))
  File "/Users/davep/develop/python/textual-sandbox/.venv/lib/python3.10/site-packages/rich/pretty.py", line 615, in iter_rich_args
    for arg in rich_args:
  File "/Users/davep/develop/python/textual-sandbox/.venv/lib/python3.10/site-packages/textual/_worker_manager.py", line 46, in __rich_repr__
    for state, count in sorted(counter.items()):
TypeError: '<' not supported between instances of 'WorkerState' and 'WorkerState'
sys:1: RuntimeWarning: coroutine 'Worker._run' was never awaited
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

when the inner worker is called (actually you just get a terminal bell and the app stops doing anything; you see the exception if you break out of the application).

This is easily worked around by running the inner worker with:

self.call_from_thread(self.inner)

which I think I would have done anyway had I been writing the code myself. However, in Discord Will says that it should work as written above; so I'm raising this issue as a thing to investigate further and perhaps make work.

@davep davep added bug Something isn't working enhancement New feature or request Task labels Oct 6, 2023
davep added a commit to davep/textual-sandbox that referenced this issue Oct 6, 2023
@rodrigogiraoserrao rodrigogiraoserrao self-assigned this Oct 25, 2023
rodrigogiraoserrao added a commit that referenced this issue Oct 25, 2023
Previously you couldn't call a threaded worker from another threaded worker because creating a worker was not thread safe (see #3472). However, the second threaded worker could already be called with self.call_from_thread, so we bake that in.
@github-actions
Copy link

Don't forget to star the repository!

Follow @textualizeio for Textual updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working enhancement New feature or request Task
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants