Repository navigation
DLPX-94668 a signal that stops stbtrace can interrupt its probe detach - #136
Merged
Merged
Conversation
stbtrace runs until it is signalled, and bcc detaches the probes from an atexit callback. That callback runs once the interpreter has started shutting down and with signal delivery still enabled, so a signal can land inside the detach: the command still exits 0 with a complete set of datapoints, but Python writes the interrupted callback's traceback to stderr. The os_tests stbtrace tests run the command under `timeout --signal=SIGINT` and fail on any stderr from a command that exited 0, so they go red a few percent of the time. Ignore SIGINT as soon as the first one is taken. The detach then always runs to completion, and it stays where bcc puts it rather than being reimplemented here. Each handler disarms only the signal it handles, so nothing becomes harder to stop than it is today: BCCHelper's SIGTERM handler exits the same way and is exposed the same way, and takes the same guard for SIGTERM. The `except KeyboardInterrupt` that used to be the stop path goes with it. No KeyboardInterrupt is raised once SIGINT has a handler of its own, and leaving a second, dead stop path in the file only obscures which one runs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
jfagetti-delphix
approved these changes
Oct 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Jira: DLPX-94668 stbtrace tests fail on stderr from a command that exited 0
Problem
A few percent of the time,
stbtracewrites a Python traceback to stderrafter a trace that worked. The command exits 0 with a complete set of JSON
datapoints and leaves no probe attached, but stderr carries ten lines of an
interrupted BCC cleanup:
BCC detaches the probes from an atexit callback, so the detach runs once the
interpreter has started shutting down and with signal delivery still enabled.
The signal that stops the trace can land inside it, which aborts the detach
and prints the traceback.
That makes the
os_teststest_stbtrace_*tests flaky: they stop the commandwith
timeout --preserve-status --signal=SIGINTand fail on any stderr from acommand that exited 0. On an image carrying
performance-diagnostics@1c2cf9a,
which fixed the kernel 7.0 compile break, all four subcommands fail this way
at about 4% a run.
Solution
Ignore
SIGINTas soon as the first one is taken, so bcc's detach always runsto completion. The detach stays where bcc puts it; only the window in which a
signal can interrupt it closes.
BCCHelper'sSIGTERMhandler exits the sameway and is exposed the same way, so it disarms
SIGTERMfor the same reason.Each handler disarms only the signal it handles. Disarming both would make
stbtraceand everyestatscript unstoppable bySIGTERMfor the durationof a detach, which nothing here needs.
The
except KeyboardInterruptthat used to be the stop path goes with it:once
SIGINThas a handler of its own noKeyboardInterruptis raised, and asecond, dead stop path only obscures which one runs. The templates keep theirs
— a template run directly, or generated with
stbtrace --bcc, still gets thedefault handler.
The alternative was to detach explicitly before exiting rather than leaving it
to the atexit callback.
cmd/stbtrace.pyhas no reference to theBPFobject— the templates construct it inside the code
exec'd from the template — andbcc's own callback would still run afterwards, so that is a larger change for
the same effect.
The harness side of this is a companion PR, the dlpx-qa-gate branch
projects/dlpx-94668-fix. Either changealone turns the four tests green; both are worth having, because the harness
change also covers the compiler warnings this one does not, and this one
removes a traceback that should not be printed whether or not a test reads it.
Testing Done
stbtrace vfsstopped as the test stops it, with the interrupt forced into the detachstbtrace vfsstopped as the test stops it, left to raceEvery run on the fix exited 0 with 27-43 JSON datapoints and zero bytes on
stderr, so no other signature replaced the one that went away. A third run,
30 per arm with the arms in the opposite order, saw the race on neither side
and so discriminates nothing; it is below for completeness.
PASSED, as expected · fix · forced-interrupt control on
dlpx-94668-fix-repro· local 2026-10-28T19:13Z · base 15/15 traceback, fix 0/15Waiting for the race gives a weak comparison at a 4%-7% rate, so the control
makes it deterministic: send
SIGINTto thestbtraceprocess in a tightloop for eight seconds from just before the trace is due to stop, so one
always lands during the detach.
evidence/forced-race.sh, results inevidence/forced-race-results.txtdlpx-94668-fix-repro, DCoA groupdlpx-qa-develop, AMIami-0bcab2c271642a6c0(appliance-build develop post-push 4880), kernel7.0.0-1014-dx2026100516-af96b8512-aws,performance-diagnostics 1.0.0-1delphix.2026.10.27.14.13,bpfcc 0.36.1-1+delphix.2026.06.22.15.45cmd/stbtrace.pyandlib/bcchelper.pycopied onto the engine fromdevelopor from this branch, with the installed file's md5 checked against the staged one before each armsudo -- timeout --preserve-status --signal=SIGINT --kill-after 30 15 stbtrace vfs15/15runs wrote the traceback; fix0/15, every run 0 bytes on stderr. Fisher exact one-sided p = 6.5e-09PASSED, as expected · fix · interleaved A/B, 70 runs per arm, on
dlpx-94668-fix-repro· local 2026-10-28T18:15Z · base 5/70 traceback, fix 0/70The rate as the tests see it, with no forcing. Arms alternate run by run so
both see the same engine and the same load.
evidence/ab-harness.py, results inevidence/ab-vfs-interleaved.txttraceback 5/70; fixtraceback 0/70, and 0 bytes of stderr on all 70. Fisher exact one-sided p = 0.029cmd/stbtrace.pyat the revision before review, which also disarmedSIGTERM.SIGTERMis never sent by this command, so the behaviour measured is the behaviour of this branch; the forced-interrupt control above and the reversed-arm run below both ran this branch's head.PASSED, no occurrences either side · fix · interleaved A/B, arms reversed, 30 runs per arm · local 2026-10-28T19:29Z · base 0/30 traceback, fix 0/30
The 70-run A/B always ran the base arm first within a pair, so this one runs
the fix arm first and logs the md5 of the installed
stbtraceon every row.At the measured 5-7% rate, 30 runs miss the race about one time in nine, and
that is what happened:
0/30on both arms. It rules out a mis-staged buildon the fix arm and leaves the ordering question answered only by the forced
control above, which is deterministic and therefore order-independent.
evidence/ab-vfs-reversed.txt; both arms ran this branch's headtraceback 0/30, fixtraceback 0/30; every fix-arm run 0 bytes on stderrPASSED · fix · how many interrupts the command actually gets · local 2026-10-28T19:26Z
straceon thestbtraceprocess through one unforced run: exactly oneSIGINT, fromtimeout, followed by itsSIGCONT. So the unforced case isnot a second signal from
sudoor the process group, and why one delivery cansurface inside the atexit callback is not established here. The fix does not
depend on knowing: it closes the window for any delivery, and the
finallydisarms the interrupt before shutdown begins so a delivery already pending is
taken there rather than inside the detach.
evidence/count-sigint.sh, results inevidence/sigint-count.txtPASSED, as expected · fix ·
flake8 .andflake8_stbtrace.sh· local 2026-10-28T19:21ZThe repo's PR CI (
.github/workflows/flake8.yml), run locally:Notes to Reviewers
evidence/above is this run's archived artifacts, not files in the repo.Only
vfswas measured on an engine. The signal handling is incmd/stbtrace.py, which every subcommand goes through, and the six templatesare identical in the code the fix touches; the subcommands differ only in how
many probes the detach has to walk.