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

Gracefully handle termination signals #5140

Open
davidfokkema opened this issue Oct 18, 2024 · 3 comments
Open

Gracefully handle termination signals #5140

davidfokkema opened this issue Oct 18, 2024 · 3 comments

Comments

@davidfokkema
Copy link

On macOS, and presumably similarly on *nix, you can do this:

import pathlib
import signal
import time

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


class MinimalApp(App[None]):
    _signal = None

    def on_mount(self) -> None:
        signal.signal(signalnum=signal.SIGHUP, handler=self.catch_signal)
        signal.signal(signalnum=signal.SIGTERM, handler=self.catch_signal)

    def compose(self) -> ComposeResult:
        yield Placeholder("This is a minimal app.")

    def catch_signal(self, signum, frame) -> None:
        self._signal = signum
        self.exit()


app = MinimalApp()
app.run()

pathlib.Path("signal.txt").write_text(
    f"{time.monotonic()}: Received signal: {app._signal}"
)
print("Done")
print(f"Received signal: {app._signal}")

This will gracefully shutdown the app when the app is killed (with SIGTERM) or when the terminal is closed (sending a SIGHUP to child processes). Currently, in the case of SIGTERM, Textual does shutdown, but not gracefully and stdin/stdout are messed up. Default signal handlers would improve that.

On Windows, it might be a bit more difficult (maybe this would work: https://stackoverflow.com/questions/69323194/how-do-i-handle-windows-terminal-closing-event-in-python) and even on Linux there are reports it might be not as straightforward as hoped. Still, might be worth it?

Copy link

We found the following entry in the FAQ which you may find helpful:

Feel free to close this issue if you found an answer in the FAQ. Otherwise, please give us a little time to review.

This is an automated reply, generated by FAQtory

@mzebrak
Copy link

mzebrak commented Oct 24, 2024

Have you tried registering the signal handler on the asyncio loop itself?

What we do is:

    def on_load(self) -> None:
        self._register_quit_signals()

    def _register_quit_signals(self) -> None:
        import signal

        def callback() -> None:
            self.exit()

        loop = asyncio.get_running_loop()  # can't use self._loop since it's not set yet
        for signal_number in [signal.SIGHUP, signal.SIGINT, signal.SIGQUIT, signal.SIGTERM]:
            loop.add_signal_handler(signal_number, callback)

Maybe this will work for you as a temporary workaround?
We haven't observed the mentioned issues with stdin/stdout with this approach.

@davidfokkema
Copy link
Author

Hi @mzebrak, I probably wasn't clear enough. We only observe the stdin/stdout issues on 'regular' Textual apps. If we install the signal handlers as shown in my code, the issue disappears. That's why I think Textual should maybe install these event handlers by default. Your code shows another way of installing the signal handlers, which is also great, but requires work on the part of the app author.

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

No branches or pull requests

2 participants