tools/ustat: Properly handle SIGINT in ustat#5498
tools/ustat: Properly handle SIGINT in ustat#5498jeromemarchand wants to merge 1 commit intoiovisor:masterfrom
Conversation
Most interactive tools waiting for a keyboard interrupt just expect it
during sleep() call. In most case it works well enough since most of
the time is typically spend in sleep. In the case of tclstat however, the
tool can spend a lot of time detaching uprobes, in self.bpf.cleanup() more
specifically and it fails when CTRL+C is pressed.
Fix this by catching the SIGINT signal. We still want to raise
KeyboardInterrupt while sleeping though, for the tool to exit
immediately when possible.
Fixes the following error:
10:12:42 loadavg: 0.03 0.05 0.01 2/289 21928
PID CMDLINE METHOD/s GC/s OBJNEW/s CLOAD/s EXC/s THR/s
21926 tclsh ./fib.tcl 2 2 0 8 0 0 0
^Cioctl(PERF_EVENT_IOC_DISABLE) failed: Bad file descriptor
close perf event FD failed: Bad file descriptor
Exception ignored in atexit callback: <bound method BPF.cleanup of <bcc.BPF object at 0x7f00481bbc20>>
Traceback (most recent call last):
File "/usr/lib/python3.12/site-packages/bcc/__init__.py", line 1855, in cleanup
self.detach_uprobe_event(k)
File "/usr/lib/python3.12/site-packages/bcc/__init__.py", line 1507, in detach_uprobe_event
raise Exception("Failed to detach BPF from uprobe")
Exception: Failed to detach BPF from uprobe
Signed-off-by: Jerome Marchand <jmarchan@redhat.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the ustat tool loop to handle SIGINT more safely, aiming to avoid failures when Ctrl-C happens during lengthy probe cleanup (notably with Tcl uprobes).
Changes:
- Add a custom SIGINT handler to control when
KeyboardInterruptis raised. - Track whether the tool is currently sleeping to preserve “exit immediately” behavior during
sleep(). - Install the SIGINT handler during
run().
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def _sigint_handler(self, signum, frame): | ||
| self.exiting = True; | ||
| if self.in_sleep: | ||
| raise KeyboardInterrupt |
There was a problem hiding this comment.
_sigint_handler reads self.in_sleep, but in_sleep is only set inside _loop_iter. If SIGINT arrives before the first iteration (or between iterations), this will raise AttributeError from inside the signal handler. Initialize self.in_sleep = False before installing the handler (e.g., in run()), or use getattr(self, 'in_sleep', False) in the handler.
| self.in_sleep = False | ||
| except KeyboardInterrupt: | ||
| self.exiting = True |
There was a problem hiding this comment.
If sleep() is interrupted, execution jumps to the except KeyboardInterrupt block and self.in_sleep stays True. That can cause later SIGINTs (e.g., during detach/cleanup) to incorrectly raise KeyboardInterrupt, reintroducing the cleanup failure this PR is trying to avoid. Ensure self.in_sleep is always reset (e.g., move it into a finally block).
| self.in_sleep = False | |
| except KeyboardInterrupt: | |
| self.exiting = True | |
| except KeyboardInterrupt: | |
| self.exiting = True | |
| finally: | |
| self.in_sleep = False |
| def _loop_iter(self): | ||
| self._attach_probes() | ||
| try: | ||
| self.in_sleep = True | ||
| sleep(self.args.interval) | ||
| self.in_sleep = False |
There was a problem hiding this comment.
With the custom SIGINT handler, SIGINT received outside the sleep() window (e.g., during _attach_probes() or printing) sets self.exiting = True but _loop_iter() will still call sleep(self.args.interval) on that iteration. Consider checking self.exiting before sleeping so Ctrl-C exits promptly when possible (while still allowing cleanup to run).
| self.bpf = None | ||
|
|
||
| def _sigint_handler(self, signum, frame): | ||
| self.exiting = True; |
There was a problem hiding this comment.
self.exiting = True; ends with a semicolon, which triggers pycodestyle E703 (and scripts/py-style-check.sh runs pycodestyle over tools/**/*.py). Remove the trailing semicolon to avoid CI/style check failures.
| self.exiting = True; | |
| self.exiting = True |
Most interactive tools waiting for a keyboard interrupt just expect it during sleep() call. In most case it works well enough since most of the time is typically spend in sleep. In the case of tclstat however, the tool can spend a lot of time detaching uprobes, in self.bpf.cleanup() more specifically and it fails when CTRL+C is pressed.
Fix this by catching the SIGINT signal. We still want to raise KeyboardInterrupt while sleeping though, for the tool to exit immediately when possible.
Fixes the following error:
10:12:42 loadavg: 0.03 0.05 0.01 2/289 21928
PID CMDLINE METHOD/s GC/s OBJNEW/s CLOAD/s EXC/s THR/s
21926 tclsh ./fib.tcl 2 2 0 8 0 0 0
^Cioctl(PERF_EVENT_IOC_DISABLE) failed: Bad file descriptor
close perf event FD failed: Bad file descriptor
Exception ignored in atexit callback: <bound method BPF.cleanup of <bcc.BPF object at 0x7f00481bbc20>>
Traceback (most recent call last):
File "/usr/lib/python3.12/site-packages/bcc/init.py", line 1855, in cleanup
self.detach_uprobe_event(k)
File "/usr/lib/python3.12/site-packages/bcc/init.py", line 1507, in detach_uprobe_event
raise Exception("Failed to detach BPF from uprobe")
Exception: Failed to detach BPF from uprobe