From 0cc11e6bfb1f4468efdc03390edd45fa5de23db1 Mon Sep 17 00:00:00 2001 From: x12sa Date: Tue, 7 Jul 2026 21:56:58 +0200 Subject: [PATCH] - ConsoleButtonsWidget ABORT: SIGINT to the client (only if the parent process looks like a BEC client), plus a delayed (500 ms) backup stop_devices request, defaulting to stop-all so the widget doubles as a generic emergency stop when opened standalone from the launcher menu --- .../console_buttons/console_buttons.py | 48 ++++++++++++++----- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/csaxs_bec/bec_widgets/widgets/console_buttons/console_buttons.py b/csaxs_bec/bec_widgets/widgets/console_buttons/console_buttons.py index e0ee7bc..515919e 100644 --- a/csaxs_bec/bec_widgets/widgets/console_buttons/console_buttons.py +++ b/csaxs_bec/bec_widgets/widgets/console_buttons/console_buttons.py @@ -26,10 +26,13 @@ class ConsoleButtonsWidget(BECWidget, QWidget): parent of the GUI server process), equivalent to pressing Ctrl+C in the console. This works even if the client is blocked inside a motor move or other long call, since it is a real OS signal rather than a - polled flag. As a backup, 500 ms later a device stop request is - published to the device server (same mechanism as the PositionerBox - stop button), so the hardware is stopped even if the client process - is hung or dead. + polled flag. The SIGINT is only sent if the parent process actually + looks like a BEC client -- it does not when the widget is opened + standalone from the launcher menu. In addition, 500 ms later a device + stop request is published to the device server (same mechanism as the + PositionerBox stop button): by default for ALL devices, so the widget + acts as a generic emergency stop from any context, even if the client + process is hung or dead. """ USER_ACCESS = ["message", "message.setter", "response", "clear_response"] @@ -37,11 +40,13 @@ class ConsoleButtonsWidget(BECWidget, QWidget): def __init__(self, parent=None, **kwargs): # Devices for which a backup stop request is published when ABORT is - # pressed (in addition to the SIGINT). An empty list means "stop ALL - # devices". For flomni, stopping ftransy is enough for the sample - # transfer: XQ#STOP is controller-wide, so it aborts all axes on - # transfer controller 1 and halts the #GRGET/#GRPUT thread. - self._backup_stop_devices = list(kwargs.pop("backup_stop_devices", ["ftransy"])) + # pressed. An empty list (default) means "stop ALL devices" -- the + # same device-server path BEC uses on scan abort -- which makes the + # widget a generic emergency stop, e.g. when opened standalone from + # the launcher menu. Stop-all also covers the flomni sample transfer: + # XQ#STOP via ftransy is controller-wide and halts the #GRGET/#GRPUT + # thread. Pass an explicit list to restrict the stop. + self._backup_stop_devices = list(kwargs.pop("backup_stop_devices", [])) super().__init__(parent=parent, **kwargs) self._response = "" # Captured once at construction time: the GUI server process is a @@ -93,10 +98,31 @@ class ConsoleButtonsWidget(BECWidget, QWidget): def _on_no(self): self._response = "no" + def _client_is_bec_process(self) -> bool: + """ + Heuristic check whether the parent process (captured at construction + time) is a BEC IPython client. Only then does SIGINT make sense: when + the widget is opened from the standalone launcher instead of a + client-spawned GUI server, the parent is e.g. a shell or the launcher + process, which must not receive the signal. + """ + try: + with open(f"/proc/{self._client_pid}/cmdline", "rb") as f: + cmdline = f.read().replace(b"\x00", b" ").decode(errors="ignore") + except OSError: + return False + return ("bec" in cmdline) or ("ipython" in cmdline) + @SafeSlot() def _on_abort(self): - logger.warning(f"ConsoleButtonsWidget: sending SIGINT to client pid {self._client_pid}") - os.kill(self._client_pid, signal.SIGINT) + if self._client_is_bec_process(): + logger.warning(f"ConsoleButtonsWidget: sending SIGINT to client pid {self._client_pid}") + os.kill(self._client_pid, signal.SIGINT) + else: + logger.warning( + "ConsoleButtonsWidget: parent process does not look like a BEC client;" + " skipping SIGINT and only sending the device stop request." + ) # Backup: direct device stop via the device server, independent of # the client process. Delayed so the SIGINT-triggered abort handler # in the client (which still sees mntprgs=1 and aborts in a