Signal being ignored / signal handler not being invoked
I'm controlling a textual app via systemd. When I stop the app, I want some cleanup to run. Systemd is sending a SIGTERM. How can I catch that in the textual app? It seems to ignore it. When I hit ctrl +c, the on_unmount action is run (which runs the shutdown method), but not when systemd terminates the app.
I also tried adding a signal handler like so:
def on_mount(self) -> None:
loop = asyncio.get_running_loop()
loop.add_signal_handler(
signal.SIGTERM, lambda: asyncio.create_task(self.shutdown())
)
Here is the shutdown method:
async def shutdown(self, exit: bool = True) -> None:
if self.webserver_port:
await self.update_firewall("remove", self.webserver_port)
self.webserver_port = 0
if exit:
self.exit()
I tried testing this manually by sending kill -s SIGTERM pid from the terminal, but that doesn't do anything. At the least, even without a signal handler, I would expect textual to exit.
it seems textual is ignoring signals, and overriding any handlers that get added? Is there a more "textual" way of handling signals?
Thanks.
Textual Diagnostics
Versions
| Name | Value |
|---|---|
| Textual | 0.82.0 |
| Rich | 13.9.2 |
Python
| Name | Value |
|---|---|
| Version | 3.12.3 |
| Implementation | CPython |
| Compiler | GCC 13.2.0 |
| Executable | /usr/lib/flux_config/.venv/bin/python |
Operating System
| Name | Value |
|---|---|
| System | Linux |
| Release | 6.8.0-45-generic |
| Version | #45-Ubuntu SMP PREEMPT_DYNAMIC Fri Aug 30 12:02:04 UTC 2024 |
Terminal
| Name | Value |
|---|---|
| Terminal Application | Unknown |
| TERM | xterm-256color |
| COLORTERM | Not set |
| FORCE_COLOR | Not set |
| NO_COLOR | Not set |
Rich Console options
| Name | Value |
|---|---|
| size | width=131, height=41 |
| legacy_windows | False |
| min_width | 1 |
| max_width | 131 |
| is_terminal | True |
| encoding | utf-8 |
| max_height | 41 |
| justify | None |
| overflow | None |
| no_wrap | False |
| highlight | None |
| markup | None |
| height | None |
Thank you for your issue. Give us a little time to review it.
PS. You might want to check the FAQ if you haven't done so already.
This is an automated reply, generated by FAQtory
Maybe this helps:
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}")
Yeah that works, wonder why the add_signal_handler doesn't?
Thanks!
Not sure, but you create a separate task for the shutdown, and that will run besides the Textual event loop? Not sure how Textual schedules events...
This works for SIGTERM, but I cannot get it to work for SIGINT or SIGTSTP.
Setting TEXTUAL_ALLOW_SIGNALS=1 works: https://github.com/Textualize/textual/blob/da3f608fd7dab10be67f5a4cdf18c57b37b7ca6f/src/textual/drivers/linux_driver.py#L321-L324
TEXTUAL_ALLOW_SIGNALS seems to be undocumented according to Google and Github searches. I have found this by grepping the codebase for signal.