Skip to content

tools/ustat: Properly handle SIGINT in ustat#5498

Open
jeromemarchand wants to merge 1 commit intoiovisor:masterfrom
jeromemarchand:ustat-sigint
Open

tools/ustat: Properly handle SIGINT in ustat#5498
jeromemarchand wants to merge 1 commit intoiovisor:masterfrom
jeromemarchand:ustat-sigint

Conversation

@jeromemarchand
Copy link
Copy Markdown
Contributor

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

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>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 KeyboardInterrupt is 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.

Comment thread tools/lib/ustat.py
Comment on lines +246 to +249
def _sigint_handler(self, signum, frame):
self.exiting = True;
if self.in_sleep:
raise KeyboardInterrupt
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

_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.

Copilot uses AI. Check for mistakes.
Comment thread tools/lib/ustat.py
Comment on lines +256 to 258
self.in_sleep = False
except KeyboardInterrupt:
self.exiting = True
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

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).

Suggested change
self.in_sleep = False
except KeyboardInterrupt:
self.exiting = True
except KeyboardInterrupt:
self.exiting = True
finally:
self.in_sleep = False

Copilot uses AI. Check for mistakes.
Comment thread tools/lib/ustat.py
Comment on lines 251 to +256
def _loop_iter(self):
self._attach_probes()
try:
self.in_sleep = True
sleep(self.args.interval)
self.in_sleep = False
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
Comment thread tools/lib/ustat.py
self.bpf = None

def _sigint_handler(self, signum, frame):
self.exiting = True;
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
self.exiting = True;
self.exiting = True

Copilot uses AI. Check for mistakes.
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