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

LinuxDriver: Exit if thread dies #3431

Merged
merged 5 commits into from
Oct 11, 2023

Conversation

rndusr
Copy link
Contributor

@rndusr rndusr commented Sep 29, 2023

If run_input_thread() dies, the whole application keeps running but becomes
unresponsive. It has to be killed by the user, leaving the terminal in an
unusable state.

This commit terminates the application instead and prints a traceback.

If `run_input_thread()` dies, the whole application keeps running but becomes
unresponsive. It has to be killed by the user, leaving the terminal in an
unusable state.

This commit terminates the application instead and prints a traceback.
@willmcgugan
Copy link
Collaborator

And what exception were you getting?

@rndusr
Copy link
Contributor Author

rndusr commented Sep 29, 2023

Sorry, I should've explained my motivation better.

I'm trying to add support for Alt keybindings (#3327). Every time I change the
code, run textual keys and it turns out I made mistake, I have to manually
kill textual keys in another terminal, and run reset to get the terminal
back into a defined state.

This commit fixes my personal situation, but I made a PR because I think it is
generally a good idea to escalate if a vital thread dies.

log(error)
self._app.exit(
return_code=1,
message=rich.traceback.Traceback.from_exception(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes more sense to call app.panic here. But bear in mind that neither exit or panic are threadsafe. It's probably wise to use app.call_later to ensure that panic is called in the main thread.

BTW I don't think you'll need all those parameters to Traceback. If you construct Traceback() in a handler it will detect the traceback automatically.

@willmcgugan
Copy link
Collaborator

This commit fixes my personal situation, but I made a PR because I think it is generally a good idea to escalate if a vital thread dies.

Got it!

@rndusr
Copy link
Contributor Author

rndusr commented Oct 6, 2023

I think it's better use an exception handler. I think this is the better approach because any exception from run_input_thread() is now properly handled and run_input_thread() doesn't have to catch it and deal with application shutdown at all. What do you think?

I've used self._app.exit() because self._app.panic() exits with 0, indicating success. But that's probably a different issue with App.panic(). I'm calling self._app.panic() now.

I'm not sure which docstring style is used and how to reference arguments, methods, etc. I couldn't find anything in CONTRIBUTING.

@@ -23,6 +24,25 @@
from ..app import App


class ExceptionHandlingThread(Thread):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extending Thread doesn't seem warranted here. Could we not wrap self.run_input_thread with a try except?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what ExceptionHandlingThread is doing. You can't catch the exception normally because it is called by Thread.start().

Or do you mean we could wrap the body of run_input_thread() in try ... except? That would work, but it makes run_input_thread() more complex than it needs to be and do things (terminating the application) it shouldn't care about. It also means that, if anyone ever would add code above the try ... except, any exception raised by that new code would be ignored. I think separating the functionality of the input thread and the termination of the application if that input thread raises makes maintenance much easier.

We could also pass a new method as the Thread target (e.g. _handle_run_input_thread()) that only catches exceptions from run_input_thread() and terminates the application in that case. That would make the separation of functionality more clear without an additional class.

We could also set threading.exceptionhook to a function that terminates the application. But that would affect all threads in the entire application. I don't think that's a good idea because developers might want to set that themselves. Breaking textual by configuring exception in your application would a pretty annoying bug. We would also have to bump the minimum Python version to 3.8.

The benefit of ExceptionHandlingThread is that it could be used by all textual threads to always terminate gracefully without affecting application developers' exception handling. I didn't look into using ExceptionHandlingThread for other threads because textual's code base is a bit daunting and I'm not sure what I should consider.

If you really don't like the new class, I'd recommend the _handle_run_input_thread() wrapper method. The other solutions wouldn't really solve the issue for good.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ws proposing a wrapper. I would just call it _run_input_thread.

@rndusr
Copy link
Contributor Author

rndusr commented Oct 10, 2023 via email

@@ -9,9 +9,10 @@
import tty
from codecs import getincrementaldecoder
from threading import Event, Thread
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, Callable
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need Callable ?

@rndusr
Copy link
Contributor Author

rndusr commented Oct 11, 2023 via email

@willmcgugan
Copy link
Collaborator

We generally rely on the IDE to catch those things.

@willmcgugan willmcgugan merged commit 08b49b1 into Textualize:main Oct 11, 2023
@rndusr
Copy link
Contributor Author

rndusr commented Oct 11, 2023 via email

@willmcgugan
Copy link
Collaborator

Most of those are erroneous. The ones that aren't don't impact the project at all. There is a place for linters, but this rule just creates busy work with no return.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants