Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix native thread sample regression from PROF-13072 guard
Replace the permanent skip (JVMThread::current()==nullptr) with a
one-shot init-window countdown per thread. JVM threads in the race
window get one signal skipped; pure native threads (where
JVMThread::current() is always null, e.g. NativeThreadCreator) are
allowed through after the window expires, restoring their samples.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
  • Loading branch information
jbachorik and claude committed Apr 10, 2026
commit abf7845d175e869c7ac1006e2a0b2cc98ab1e985
15 changes: 7 additions & 8 deletions ddprof-lib/src/main/cpp/ctimer_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,14 +157,13 @@ void CTimer::signalHandler(int signo, siginfo_t *siginfo, void *ucontext) {
int tid = 0;
ProfiledThread *current = ProfiledThread::currentSignalSafe();
assert(current == nullptr || !current->isDeepCrashHandler());
// If the JVM has initialized its thread TLS key but this thread has not yet
// called pd_set_thread() (race window between Profiler::registerThread() and
// thread_native_entry), Thread::current() inside ASGCT returns nullptr and
// crashes in JFR allocation paths. Skip the sample instead.
// Side-effect: threads that never call pd_set_thread() (non-JVM native threads
// that were created before the JVM initialized) are also skipped, which is
// acceptable given the crash severity.
if (current != nullptr && JVMThread::isInitialized() && JVMThread::current() == nullptr) {
// Guard against the race window between Profiler::registerThread() and
// thread_native_entry setting JVM TLS (PROF-13072): skip at most one signal
// per thread. Pure native threads (where JVMThread::current() is always null)
// are allowed through once the one-shot window expires.
if (current != nullptr && JVMThread::isInitialized() && JVMThread::current() == nullptr
&& current->inInitWindow()) {
current->tickInitWindow();
errno = saved_errno;
return;
}
Expand Down
2 changes: 2 additions & 0 deletions ddprof-lib/src/main/cpp/libraryPatcher_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ static void* start_routine_wrapper_spec(void* args) {
delete_routine_info(thr);
init_thread_tls();
tid = ProfiledThread::currentTid();
ProfiledThread::currentSignalSafe()->startInitWindow();
Profiler::registerThread(tid);
}
void* result = routine(params);
Expand Down Expand Up @@ -154,6 +155,7 @@ static void* start_routine_wrapper(void* args) {
delete thr;
ProfiledThread::initCurrentThread();
tid = ProfiledThread::currentTid();
ProfiledThread::currentSignalSafe()->startInitWindow();
Comment thread
jbachorik marked this conversation as resolved.
Profiler::registerThread(tid);
}
void* result = nullptr;
Expand Down
15 changes: 13 additions & 2 deletions ddprof-lib/src/main/cpp/thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class ProfiledThread : public ThreadLocalData {
u32 _recording_epoch;
u32 _misc_flags;
int _filter_slot_id; // Slot ID for thread filtering
uint8_t _init_window; // Countdown for JVM thread init race window (PROF-13072)
Comment thread
jbachorik marked this conversation as resolved.
UnwindFailures _unwind_failures;
bool _otel_ctx_initialized;
bool _crash_protection_active;
Expand All @@ -77,7 +78,8 @@ class ProfiledThread : public ThreadLocalData {

ProfiledThread(int buffer_pos, int tid)
: ThreadLocalData(), _pc(0), _sp(0), _span_id(0), _crash_depth(0), _buffer_pos(buffer_pos), _tid(tid), _cpu_epoch(0),
_wall_epoch(0), _call_trace_id(0), _recording_epoch(0), _misc_flags(0), _filter_slot_id(-1), _otel_ctx_initialized(false), _crash_protection_active(false),
_wall_epoch(0), _call_trace_id(0), _recording_epoch(0), _misc_flags(0), _filter_slot_id(-1), _init_window(0),
_otel_ctx_initialized(false), _crash_protection_active(false),
_otel_ctx_record{}, _otel_tag_encodings{}, _otel_local_root_span_id(0) {};

virtual ~ProfiledThread() { }
Expand Down Expand Up @@ -176,7 +178,16 @@ class ProfiledThread : public ThreadLocalData {

int filterSlotId() { return _filter_slot_id; }
void setFilterSlotId(int slotId) { _filter_slot_id = slotId; }


// JVM thread init race window (PROF-13072): skip at most one signal that fires
// between Profiler::registerThread() and the JVM's pd_set_thread() call.
// Pure native threads (e.g. NativeThreadCreator) also see nullptr from
// JVMThread::current(), so the window auto-expires after one skip, allowing
// their subsequent samples through.
inline bool inInitWindow() const { return _init_window > 0; }
inline void startInitWindow() { _init_window = 1; }
inline void tickInitWindow() { if (_init_window > 0) --_init_window; }

// Signal handler reentrancy protection
bool tryEnterCriticalSection() {
// Uses GCC atomic builtin (no malloc, async-signal-safe)
Expand Down
8 changes: 6 additions & 2 deletions ddprof-lib/src/main/cpp/wallClock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,12 @@ void WallClockASGCT::signalHandler(int signo, siginfo_t *siginfo, void *ucontext
}
ProfiledThread *current = ProfiledThread::currentSignalSafe();
// Guard against the race window between Profiler::registerThread() and
// thread_native_entry setting JVM TLS (see PROF-13072 / CTimer::signalHandler).
if (current != nullptr && JVMThread::isInitialized() && JVMThread::current() == nullptr) {
// thread_native_entry setting JVM TLS (PROF-13072): skip at most one signal
// per thread. Pure native threads (where JVMThread::current() is always null)
// are allowed through once the one-shot window expires.
if (current != nullptr && JVMThread::isInitialized() && JVMThread::current() == nullptr
&& current->inInitWindow()) {
current->tickInitWindow();
return;
}
int tid = current != NULL ? current->tid() : OS::threadId();
Expand Down
Loading