Skip to content

Commit 8b069cb

Browse files
committed
docs(signalInflight): document longjmp-leak limitation
InflightGuard's destructor can be bypassed if the signal handler frame is unwound by a longjmp. In this codebase the only realistic trigger is J9's SIGSEGV null-pointer-check handler chaining out of our segvHandler via siglongjmp to a setjmp in normal Java code. Probability is very low (J9 would have to claim a fault our stack walker produced, which lies in our own C++ code at addresses J9 has no reason to recognise) and the failure mode is graceful: a stuck counter makes every subsequent stop() hit the drain timeout and skip JFR teardown — the safety net this counter exists to provide. Document this explicitly rather than build out a workaround. Matches the existing precedent in guards.h, where SignalHandlerScope's own depth counter has the same limitation on the same J9 chain path.
1 parent 97e9f99 commit 8b069cb

1 file changed

Lines changed: 20 additions & 0 deletions

File tree

ddprof-lib/src/main/cpp/signalInflight.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,26 @@
2828
// The counter is on its own cache line (alignas(64)) so that handler-side
2929
// writes do not invalidate the cache line backing each engine's _enabled
3030
// flag, which is read on every signal.
31+
//
32+
// Known limitation — longjmp out of a signal handler frame:
33+
// If a signal handler frame is unwound by a longjmp that bypasses the
34+
// InflightGuard destructor, the counter leaks by +1 permanently. In this
35+
// codebase that can only happen via J9's SIGSEGV null-pointer-check
36+
// handler: our segvHandler chains to J9 for unclaimed faults, and J9 may
37+
// siglongjmp to a setjmp installed in normal Java code (J9 null-check
38+
// recovery), unwinding past every frame above it including any active
39+
// InflightGuard. SignalHandlerScope has the same limitation for its own
40+
// depth counter (see guards.h) and the codebase accepts it.
41+
//
42+
// In practice this is extremely unlikely to fire from a profiler signal
43+
// handler: J9 only siglongjmps when the fault matches an annotated
44+
// null-check site in Java bytecode, and our stack walker faults occur
45+
// inside our own C++ code that J9 has no reason to claim. If it ever did
46+
// fire, the failure mode is graceful: every subsequent Profiler::stop()
47+
// hits drain()'s timeout, returns Error, and refuses to tear down JFR.
48+
// The JVM keeps running; the profiler is wedged until process exit.
49+
// No crash, no use-after-free — the timeout path that this counter exists
50+
// to gate is exactly the safety net for this case.
3151
class SignalInflight {
3252
public:
3353
// ACQUIRE on increment / RELEASE on decrement: drain() observes all

0 commit comments

Comments
 (0)