Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2d6d688
v0
zhengyu123 Jul 7, 2026
f7e3255
AI reviews
zhengyu123 Jul 7, 2026
dd2acd5
Merge branch 'main' into zgu/profiled_thread
zhengyu123 Jul 8, 2026
1793aea
Potential fix for pull request finding
zhengyu123 Jul 8, 2026
4414df3
Potential fix for pull request finding
zhengyu123 Jul 8, 2026
e74e4b3
Potential fix for pull request finding
zhengyu123 Jul 8, 2026
e1ed4cf
v1
zhengyu123 Jul 8, 2026
09ad293
Merge branch 'zgu/profiled_thread' of github.com:DataDog/java-profile…
zhengyu123 Jul 8, 2026
dfbe748
Fix test
zhengyu123 Jul 8, 2026
ac039e3
Merge
zhengyu123 Jul 8, 2026
7b9ac45
Remove ProfiledThread::currentSignalSafe()
zhengyu123 Jul 8, 2026
f766961
Pre-existed - add nullptr check
zhengyu123 Jul 8, 2026
63d7b61
Fix
zhengyu123 Jul 8, 2026
e5d59e7
Restore early behavior
zhengyu123 Jul 9, 2026
9ded4c2
Harden initCurrentThread() with initCurrentThreadSignalSafe()
zhengyu123 Jul 9, 2026
2d10883
Block signal for deleting ProfiledThread
zhengyu123 Jul 9, 2026
d59a7c2
Review comments
zhengyu123 Jul 9, 2026
00edbd4
Review comments
zhengyu123 Jul 10, 2026
800d38d
Review
zhengyu123 Jul 10, 2026
34b18f0
Profiler must be initalized on non-virtual thread
zhengyu123 Jul 10, 2026
d8f9c02
Merge branch 'main' into zgu/profiled_thread
zhengyu123 Jul 10, 2026
dcaba6a
New methods after merge
zhengyu123 Jul 10, 2026
42fd898
Comment JVMSupport::initialize()
zhengyu123 Jul 10, 2026
c4aa652
Merge branch 'main' into zgu/profiled_thread
zhengyu123 Jul 13, 2026
34db19a
Update ddprof-lib/src/main/cpp/threadLocalData.cpp
zhengyu123 Jul 13, 2026
8c3769d
Update ddprof-lib/src/main/cpp/profiler.cpp
zhengyu123 Jul 13, 2026
2636428
Update ddprof-lib/src/main/cpp/profiler.cpp
zhengyu123 Jul 13, 2026
d341e7b
Update ddprof-lib/src/main/java/com/datadoghq/profiler/JavaProfiler.java
zhengyu123 Jul 13, 2026
6b78a6c
Merge branch 'main' into zgu/profiled_thread
zhengyu123 Jul 13, 2026
7886564
Merge branch 'main' into zgu/profiled_thread
zhengyu123 Jul 14, 2026
19c0200
Merge branch 'main' into zgu/profiled_thread
zhengyu123 Jul 16, 2026
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
4 changes: 2 additions & 2 deletions ddprof-lib/src/main/cpp/context_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ void ContextApi::initializeContextTLS(ProfiledThread* thrd) {
}

bool ContextApi::get(u64& span_id, u64& root_span_id) {
ProfiledThread* thrd = ProfiledThread::currentSignalSafe();
ProfiledThread* thrd = ProfiledThread::current();
if (thrd == nullptr || !thrd->isContextInitialized()) {
return false;
}
Expand All @@ -59,7 +59,7 @@ bool ContextApi::get(u64& span_id, u64& root_span_id) {
}

Context ContextApi::snapshot() {
ProfiledThread* thrd = ProfiledThread::currentSignalSafe();
ProfiledThread* thrd = ProfiledThread::current();
if (thrd == nullptr) {
return {};
}
Expand Down
4 changes: 2 additions & 2 deletions ddprof-lib/src/main/cpp/ctimer_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ void CTimerJvmti::signalHandler(int signo, siginfo_t *siginfo, void *ucontext) {
return;
}
int tid = 0;
ProfiledThread *current = ProfiledThread::currentSignalSafe();
ProfiledThread *current = ProfiledThread::current();
assert(current == nullptr || !current->isDeepCrashHandler());
if (current != nullptr && JVMThread::current() == nullptr
&& current->inInitWindow()) {
Expand Down Expand Up @@ -281,7 +281,7 @@ void CTimer::signalHandler(int signo, siginfo_t *siginfo, void *ucontext) {
return;
}
int tid = 0;
ProfiledThread *current = ProfiledThread::currentSignalSafe();
ProfiledThread *current = ProfiledThread::current();
assert(current == nullptr || !current->isDeepCrashHandler());
// Guard against the race window between Profiler::registerThread() and
// thread_native_entry setting JVM TLS (PROF-13072): skip at most one signal
Expand Down
2 changes: 1 addition & 1 deletion ddprof-lib/src/main/cpp/flightRecorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1771,7 +1771,7 @@ void Recording::writeCurrentContext(Buffer *buf) {
buf->putVar64(rootSpanId);

size_t numAttrs = Profiler::instance()->numContextAttributes();
ProfiledThread* thrd = hasContext ? ProfiledThread::currentSignalSafe() : nullptr;
ProfiledThread* thrd = hasContext ? ProfiledThread::current() : nullptr;
for (size_t i = 0; i < numAttrs; i++) {
buf->putVar32(thrd != nullptr ? thrd->getOtelTagEncoding(i) : 0);
}
Expand Down
14 changes: 7 additions & 7 deletions ddprof-lib/src/main/cpp/guards.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,20 @@
// rejected because of the static TLS surplus on Graal).

int getInSignalDepth() {
ProfiledThread *pt = ProfiledThread::currentSignalSafe();
ProfiledThread *pt = ProfiledThread::current();
return pt != nullptr ? static_cast<int>(pt->signalDepth()) : 0;
}

bool isInTrackedSignalContext() {
ProfiledThread *pt = ProfiledThread::currentSignalSafe();
ProfiledThread *pt = ProfiledThread::current();
// null ProfiledThread = no thread context; the SignalHandlerScope
// never ran, so we have no positive evidence of a signal frame.
// See header comment for the rationale of returning false here.
return pt != nullptr && pt->signalDepth() != 0;
}

SignalHandlerScope::SignalHandlerScope() : _active(true) {
ProfiledThread *pt = ProfiledThread::currentSignalSafe();
ProfiledThread *pt = ProfiledThread::current();
if (pt != nullptr) {
pt->enterSignalScope();
} else {
Expand All @@ -49,23 +49,23 @@ SignalHandlerScope::SignalHandlerScope() : _active(true) {

SignalHandlerScope::~SignalHandlerScope() {
if (!_active) return;
ProfiledThread *pt = ProfiledThread::currentSignalSafe();
ProfiledThread *pt = ProfiledThread::current();
if (pt != nullptr) {
pt->exitSignalScope();
}
}

void SignalHandlerScope::release() {
if (!_active) return;
ProfiledThread *pt = ProfiledThread::currentSignalSafe();
ProfiledThread *pt = ProfiledThread::current();
if (pt != nullptr) {
pt->exitSignalScope();
}
_active = false;
}

void signalHandlerUnwindAfterLongjmp() {
ProfiledThread *pt = ProfiledThread::currentSignalSafe();
ProfiledThread *pt = ProfiledThread::current();
if (pt != nullptr) {
pt->exitSignalScope();
}
Expand All @@ -75,7 +75,7 @@ void signalHandlerUnwindAfterLongjmp() {
uint64_t CriticalSection::_fallback_bitmap[CriticalSection::FALLBACK_BITMAP_WORDS] = {};

CriticalSection::CriticalSection() : _entered(false), _using_fallback(false), _word_index(0), _bit_mask(0), _thread_ptr(nullptr) {
_thread_ptr = ProfiledThread::currentSignalSafe();
_thread_ptr = ProfiledThread::current();
if (_thread_ptr != nullptr) {
// Primary path: Use ProfiledThread storage (fast and memory-efficient)
_entered = _thread_ptr->tryEnterCriticalSection();
Expand Down
6 changes: 3 additions & 3 deletions ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex
// VMStructs is only available for hotspot JVM
assert(VM::isHotspot());

ProfiledThread* prof_thread = ProfiledThread::currentSignalSafe();
ProfiledThread* prof_thread = ProfiledThread::current();
if (prof_thread == nullptr) {
Counters::increment(SAMPLES_DROPPED_THREAD_LOCAL);
return 0;
Expand Down Expand Up @@ -1198,7 +1198,7 @@ int HotspotSupport::walkJavaStack(StackWalkRequest& request) {
if (cstack >= CSTACK_VM) {
java_frames = walkVM(ucontext, frames, max_depth, features, eventTypeFromBCI(request.event_type), lock_index, truncated);
} else {
AsyncSampleMutex mutex(ProfiledThread::currentSignalSafe());
AsyncSampleMutex mutex(ProfiledThread::current());
if (mutex.acquired()) {
java_frames = getJavaTraceAsync(ucontext, frames, max_depth, java_ctx, truncated);
if (java_frames > 0 && java_ctx->pc != NULL && VMStructs::hasMethodStructs()) {
Expand All @@ -1223,7 +1223,7 @@ int HotspotSupport::walkJavaStack(StackWalkRequest& request) {
java_frames = walkVM(ucontext, frames, max_depth, features, eventTypeFromBCI(request.event_type), lock_index, truncated);
} else {
// Async events
AsyncSampleMutex mutex(ProfiledThread::currentSignalSafe());
AsyncSampleMutex mutex(ProfiledThread::current());
if (mutex.acquired()) {
java_frames = getJavaTraceAsync(ucontext, frames, max_depth, java_ctx, truncated);
if (java_frames > 0 && java_ctx->pc != NULL && VMStructs::hasMethodStructs()) {
Expand Down
2 changes: 1 addition & 1 deletion ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,7 @@ bool VMThread::isJavaThread(VMThread* vm_thread) {

// JVMTI ThreadStart callback may have set the flag, which is reliable.
// Or we may already compute and cache it, so use it instead.
ProfiledThread *prof_thread = ProfiledThread::currentSignalSafe();
ProfiledThread *prof_thread = ProfiledThread::current();
if (prof_thread != nullptr) {
ProfiledThread::ThreadType type = prof_thread->threadType();
if (type != ProfiledThread::ThreadType::TYPE_UNKNOWN) {
Expand Down
2 changes: 1 addition & 1 deletion ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
#include "threadLocalData.h"

inline bool crashProtectionActive() {
ProfiledThread* pt = ProfiledThread::currentSignalSafe();
ProfiledThread* pt = ProfiledThread::current();
if (pt != nullptr) {
return pt->isProtected();
}
Expand Down
4 changes: 2 additions & 2 deletions ddprof-lib/src/main/cpp/itimer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ void ITimer::signalHandler(int signo, siginfo_t *siginfo, void *ucontext) {
return; // Another critical section is active, defer profiling
}
int tid = 0;
ProfiledThread *current = ProfiledThread::currentSignalSafe();
ProfiledThread *current = ProfiledThread::current();
if (current != NULL) {
current->noteCPUSample(Profiler::instance()->recordingEpoch());
tid = current->tid();
Expand Down Expand Up @@ -116,7 +116,7 @@ void ITimerJvmti::signalHandler(int signo, siginfo_t *siginfo, void *ucontext) {
errno = saved_errno;
return;
}
ProfiledThread *current = ProfiledThread::currentSignalSafe();
ProfiledThread *current = ProfiledThread::current();
if (current != nullptr && JVMThread::current() == nullptr
&& current->inInitWindow()) {
current->tickInitWindow();
Expand Down
54 changes: 49 additions & 5 deletions ddprof-lib/src/main/cpp/javaApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "engine.h"
#include "hotspot/vmStructs.inline.h"
#include "incbin.h"
#include "jvmSupport.h"
#include "jvmThread.h"
#include "os.h"
#include "otel_process_ctx.h"
Expand Down Expand Up @@ -68,6 +69,12 @@ class JniString {

extern "C" DLLEXPORT jboolean JNICALL
Java_com_datadoghq_profiler_JavaProfiler_init0(JNIEnv *env, jclass unused) {
Error error = Profiler::instance()->init();
Comment thread
zhengyu123 marked this conversation as resolved.
Comment thread
zhengyu123 marked this conversation as resolved.
if (error) {
throwNew(env, "java/lang/IllegalStateException", error.message());
return JNI_FALSE;
}

Comment thread
zhengyu123 marked this conversation as resolved.
// JavaVM* has already been stored when the native library was loaded so we can pass nullptr here
return VM::initProfilerBridge(nullptr, true);
}
Comment thread
Copilot marked this conversation as resolved.
Expand Down Expand Up @@ -129,6 +136,22 @@ Java_com_datadoghq_profiler_JavaProfiler_getSamples(JNIEnv *env,
return (jlong)Profiler::instance()->total_samples();
}

// Init or get current profiled thread.
// Calling thread's thread local may not be initialized due to race.
// Especially, during the early startup phase.
// This call could be expensive, if TLS has not yet set.
// Note: the racy can be avoided with native agent, remove this method
// once converted to native agent.
// TODO: Priming is not fully restored at this moment, this method only
// handles a few special cases.
static ProfiledThread* initOrGetCurrentThread() {
ProfiledThread* current = ProfiledThread::current();
if (current == nullptr) {
current = ProfiledThread::initCurrentThreadSignalSafe();
}
return current;
}

// some duplication between add and remove, though we want to avoid having an extra branch in the hot path

// JavaCritical is faster JNI, but more restrictive - parameters and return value have to be
Expand All @@ -137,8 +160,12 @@ Java_com_datadoghq_profiler_JavaProfiler_getSamples(JNIEnv *env,
// still compatible in the event of signature changes in the future.
extern "C" DLLEXPORT void JNICALL
JavaCritical_com_datadoghq_profiler_JavaProfiler_filterThreadAdd0() {
ProfiledThread *current = ProfiledThread::current();
assert(current != nullptr);
// Initialize thread TLS if it has not yet done
ProfiledThread *current = initOrGetCurrentThread();
if(current == nullptr) {
return;
}
Comment thread
zhengyu123 marked this conversation as resolved.

int tid = current->tid();
if (unlikely(tid < 0)) {
return;
Expand Down Expand Up @@ -167,8 +194,12 @@ JavaCritical_com_datadoghq_profiler_JavaProfiler_filterThreadAdd0() {

extern "C" DLLEXPORT void JNICALL
JavaCritical_com_datadoghq_profiler_JavaProfiler_filterThreadRemove0() {
ProfiledThread *current = ProfiledThread::current();
assert(current != nullptr);
// Initialize thread TLS if it has not yet done
ProfiledThread *current = initOrGetCurrentThread();
if(current == nullptr) {
return;
}
Comment thread
zhengyu123 marked this conversation as resolved.

int tid = current->tid();
if (unlikely(tid < 0)) {
return;
Expand Down Expand Up @@ -204,6 +235,10 @@ Java_com_datadoghq_profiler_JavaProfiler_recordTrace0(
JNIEnv *env, jclass unused, jlong rootSpanId, jstring endpoint,
jstring operation, jint sizeLimit) {
JniString endpoint_str(env, endpoint);

// Initialize thread TLS if it has not yet done
initOrGetCurrentThread();

u32 endpointLabel = Profiler::instance()->stringLabelMap()->bounded_lookup(
endpoint_str.c_str(), endpoint_str.length(), sizeLimit);
// StringDictionary reserves 0 as "no entry"; valid IDs start at 1.
Expand Down Expand Up @@ -261,6 +296,9 @@ Java_com_datadoghq_profiler_JavaProfiler_describeDebugCounters0(
extern "C" DLLEXPORT void JNICALL
Java_com_datadoghq_profiler_JavaProfiler_recordSettingEvent0(
JNIEnv *env, jclass unused, jstring name, jstring value, jstring unit) {
// Initialize thread TLS if it has not yet done
initOrGetCurrentThread();

int tid = ProfiledThread::currentTid();
if (tid < 0) {
return;
Expand All @@ -285,6 +323,10 @@ extern "C" DLLEXPORT void JNICALL
Java_com_datadoghq_profiler_JavaProfiler_recordQueueEnd0(
JNIEnv *env, jclass unused, jlong startTime, jlong endTime, jstring task,
jstring scheduler, jthread origin, jstring queueType, jint queueLength) {

// Initialize thread TLS if it has not yet done
initOrGetCurrentThread();

int tid = ProfiledThread::currentTid();
if (tid < 0) {
return;
Expand Down Expand Up @@ -341,6 +383,7 @@ Java_com_datadoghq_profiler_JavaProfiler_parkExit0(
if (current == nullptr) {
return;
}

u64 park_block_token = 0;
if (!current->parkExit(park_block_token) || park_block_token == 0) {
return;
Expand Down Expand Up @@ -685,7 +728,8 @@ Java_com_datadoghq_profiler_OTelContext_readProcessCtx0(JNIEnv *env, jclass unus

extern "C" DLLEXPORT jobject JNICALL
Java_com_datadoghq_profiler_JavaProfiler_initializeContextTLS0(JNIEnv* env, jclass unused, jlongArray metadata) {
ProfiledThread* thrd = ProfiledThread::current();
// Initialize thread TLS if it has not yet done
ProfiledThread* thrd = initOrGetCurrentThread();
assert(thrd != nullptr);

if (!thrd->isContextInitialized()) {
Expand Down
8 changes: 4 additions & 4 deletions ddprof-lib/src/main/cpp/jvmSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ bool JVMSupport::initialize() {
return false;
}

// Add ProfiledThread key checking here in next PR
return true;
// Check ProfiledThread key, it is critical for storing per-thread metadata
return ProfiledThread::isThreadKeyValid();
}

bool JVMSupport::isInitialized() {
return JVMThread::isInitialized();
return JVMThread::isInitialized() && ProfiledThread::isThreadKeyValid();
}

JVMSupport::JMethodIDLoadStats JVMSupport::getLoadState() {
Expand Down Expand Up @@ -95,7 +95,7 @@ int JVMSupport::asyncGetCallTrace(ASGCT_CallFrame *frames, int max_depth, void*
return 0;
}

AsyncSampleMutex mutex(ProfiledThread::currentSignalSafe());
AsyncSampleMutex mutex(ProfiledThread::current());
Comment thread
zhengyu123 marked this conversation as resolved.
if (!mutex.acquired()) {
return 0;
}
Expand Down
17 changes: 10 additions & 7 deletions ddprof-lib/src/main/cpp/libraryPatcher_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,14 @@ class RoutineInfo {
// Unregister the current thread from the profiler and release its TLS under a
// single SignalBlocker to close the race window between unregisterThread()
// returning and release() acquiring its internal guard (PROF-14603). Without
// this, a SIGVTALRM delivered in that window could call currentSignalSafe()
// this, a SIGVTALRM delivered in that window could call current()
// and dereference a now-freed ProfiledThread. Kept noinline so the
// SignalBlocker's sigset_t does not appear in the caller's stack frame on
// musl/aarch64 where the deopt blob may corrupt the wrapper's stack guard.
__attribute__((noinline))
static void unregister_and_release(int tid) {
static void unregister_and_release() {
SignalBlocker blocker;
int tid = ProfiledThread::currentTid();
Profiler::unregisterThread(tid);
ProfiledThread::release();
}
Expand All @@ -83,7 +84,7 @@ static void unregister_and_release(int tid) {
// appear in the caller's frame on platforms with stack-protector canaries.
__attribute__((noinline))
static void cleanup_unregister(void*) {
unregister_and_release(ProfiledThread::currentTid());
unregister_and_release();
}

// Thread-cleanup wrapper that avoids the static-libgcc / forced-unwind crash.
Expand Down Expand Up @@ -254,8 +255,8 @@ static void delete_routine_info(RoutineInfo* thr) {
__attribute__((noinline))
static void init_tls_and_register() {
SignalBlocker blocker;
ProfiledThread::initCurrentThread();
if (ProfiledThread *pt = ProfiledThread::currentSignalSafe()) {
ProfiledThread* pt = ProfiledThread::initCurrentThread();
if (pt != nullptr) {
pt->startInitWindow();
}
Profiler::registerThread(ProfiledThread::currentTid());
Expand Down Expand Up @@ -371,8 +372,10 @@ static void* start_routine_wrapper(void* args) {
routine = thr->routine();
params = thr->args();
delete thr;
ProfiledThread::initCurrentThread();
ProfiledThread::currentSignalSafe()->startInitWindow();
ProfiledThread* pt = ProfiledThread::initCurrentThread();
if (pt != nullptr) {
pt->startInitWindow();
}
Profiler::registerThread(ProfiledThread::currentTid());
}
// Use POSIX cleanup instead of C++ RAII to handle pthread_exit(): see run_with_cleanup.
Expand Down
2 changes: 1 addition & 1 deletion ddprof-lib/src/main/cpp/perfEvents_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,7 @@ void PerfEvents::signalHandler(int signo, siginfo_t *siginfo, void *ucontext) {
if (!cs.entered()) {
return; // Another critical section is active, defer profiling
}
ProfiledThread *current = ProfiledThread::currentSignalSafe();
ProfiledThread *current = ProfiledThread::current();
if (current != NULL) {
current->noteCPUSample(Profiler::instance()->recordingEpoch());
}
Expand Down
Loading
Loading