Skip to content

Commit bfa0248

Browse files
committed
feat: squash commit for PR 664 rebase
1 parent 1fdf2c8 commit bfa0248

23 files changed

Lines changed: 2006 additions & 73 deletions

build-logic/conventions/src/main/kotlin/com/datadoghq/native/config/ConfigurationPresets.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
/*
2+
* Copyright 2026, Datadog, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
116

217
package com.datadoghq.native.config
318

ddprof-lib/src/main/cpp/javaApi.cpp

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ class JniString {
7070
};
7171

7272
extern "C" DLLEXPORT jboolean JNICALL
73-
Java_com_datadoghq_profiler_JavaProfiler_init0(JNIEnv *env, jclass unused) {
73+
Java_com_datadoghq_profiler_JavaProfiler_init0(
74+
JNIEnv *env, jclass unused, jboolean delegateMonitorWaitEvents) {
7475
Error error = Profiler::instance()->init();
7576
if (error) {
7677
throwNew(env, "java/lang/IllegalStateException", error.message());
@@ -79,13 +80,22 @@ Java_com_datadoghq_profiler_JavaProfiler_init0(JNIEnv *env, jclass unused) {
7980

8081

8182
// JavaVM* has already been stored when the native library was loaded so we can pass nullptr here
82-
if (VM::initProfilerBridge(nullptr, true)) {
83-
// Attach ProfiledThread
84-
ProfiledThread::initCurrentThreadSignalSafe();
85-
return JNI_TRUE;
86-
} else {
83+
ProfilerBridgeInitResult result =
84+
VM::initProfilerBridge(nullptr, true, delegateMonitorWaitEvents);
85+
if (result == ProfilerBridgeInitResult::MONITOR_EVENTS_DELEGATION_CONFLICT) {
86+
throwNew(env, "java/lang/IllegalStateException",
87+
"Monitor-event ownership conflicts with the profiler's "
88+
"process-wide initialization");
8789
return JNI_FALSE;
8890
}
91+
if (result != ProfilerBridgeInitResult::SUCCESS) {
92+
throwNew(env, "java/lang/IllegalStateException",
93+
"Failed to initialize the profiler bridge");
94+
return JNI_FALSE;
95+
}
96+
// Attach ProfiledThread
97+
ProfiledThread::initCurrentThreadSignalSafe();
98+
return JNI_TRUE;
8999
}
90100

91101
extern "C" DLLEXPORT void JNICALL
@@ -110,6 +120,12 @@ Java_com_datadoghq_profiler_JavaProfiler_getTid0(JNIEnv *env, jclass unused) {
110120
return OS::threadId();
111121
}
112122

123+
extern "C" DLLEXPORT jboolean JNICALL
124+
Java_com_datadoghq_profiler_JavaProfiler_monitorWaitEventsDelegated0(
125+
JNIEnv *env, jclass unused) {
126+
return VM::monitorWaitEventsDelegated();
127+
}
128+
113129
extern "C" DLLEXPORT jstring JNICALL
114130
Java_com_datadoghq_profiler_JavaProfiler_execute0(JNIEnv *env, jobject unused,
115131
jstring command) {
@@ -389,44 +405,55 @@ Java_com_datadoghq_profiler_JavaProfiler_recordQueueEnd0(
389405
}
390406

391407
extern "C" DLLEXPORT jboolean JNICALL
392-
Java_com_datadoghq_profiler_JavaProfiler_parkEnter0(JNIEnv *env, jclass unused) {
408+
Java_com_datadoghq_profiler_JavaProfiler_parkEnter0(
409+
JNIEnv *env, jclass unused, jthread thread) {
410+
if (!JVMSupport::isPlatformThread(env, thread)) {
411+
return JNI_FALSE;
412+
}
393413
ProfiledThread *current = ProfiledThread::initCurrentThreadSignalSafe();
394414
if (current == nullptr) {
395415
return JNI_FALSE;
396416
}
417+
Context context = ContextApi::snapshot();
418+
if (!current->parkEnter(TSC::ticks(), context)) {
419+
return JNI_FALSE;
420+
}
397421

398-
bool first_park = current->parkEnter();
399-
ThreadFilter *tf = Profiler::instance()->threadFilter();
400-
if (first_park && tf->registryActive()) {
422+
Profiler *profiler = Profiler::instance();
423+
ThreadFilter *tf = profiler->threadFilter();
424+
if (context.spanId == 0 && tf->registryActive() &&
425+
(profiler->taskBlockEnabled() || tf->enabled())) {
401426
ThreadFilter::SlotID slot_id = tf->ensureCurrentThreadSlot(current);
402427
if (slot_id >= 0) {
403-
current->setParkBlockToken(
404-
tf->enterBlockedRun(slot_id, OSThreadState::CONDVAR_WAIT));
428+
current->setParkBlockToken(tf->enterBlockedRun(
429+
slot_id, OSThreadState::CONDVAR_WAIT, BlockRunOwner::JAVA));
405430
}
406431
}
407-
return first_park ? JNI_TRUE : JNI_FALSE;
432+
return JNI_TRUE;
408433
}
409434

410435
extern "C" DLLEXPORT void JNICALL
411436
Java_com_datadoghq_profiler_JavaProfiler_parkExit0(
412-
JNIEnv *env, jclass unused, jlong blocker, jlong unblockingSpanId) {
437+
JNIEnv *env, jclass unused, jthread thread, jlong blocker,
438+
jlong unblockingSpanId) {
439+
if (!JVMSupport::isPlatformThread(env, thread)) {
440+
return;
441+
}
413442
ProfiledThread *current = ProfiledThread::initCurrentThreadSignalSafe();
414443
if (current == nullptr) {
415444
return;
416445
}
417-
446+
u64 start_ticks = 0;
418447
u64 park_block_token = 0;
419-
if (!current->parkExit(park_block_token) || park_block_token == 0) {
448+
Context context{};
449+
if (!current->parkExit(start_ticks, context, park_block_token) ||
450+
park_block_token == 0) {
420451
return;
421452
}
422-
ThreadFilter *tf = Profiler::instance()->threadFilter();
423-
if (tf->registryActive()) {
424-
ThreadFilter::SlotID slot_id = ThreadFilter::tokenSlotId(park_block_token);
425-
if (tf->activeSlotForId(current->filterSlotId(), current->tid()) != nullptr &&
426-
current->filterSlotId() == slot_id) {
427-
tf->exitBlockedRun(slot_id, ThreadFilter::tokenGeneration(park_block_token));
428-
}
429-
}
453+
finishTaskBlockAtExit(current, Profiler::instance()->threadFilter(), thread,
454+
1, park_block_token, start_ticks, context,
455+
static_cast<u64>(blocker),
456+
static_cast<u64>(unblockingSpanId));
430457
}
431458

432459
static bool decodeJavaBlockState(jint state, OSThreadState &decoded) {
@@ -454,13 +481,14 @@ static bool isCurrentJniThread(JNIEnv* env, jthread thread) {
454481

455482
extern "C" DLLEXPORT jlong JNICALL
456483
Java_com_datadoghq_profiler_JavaProfiler_blockEnter0(
457-
JNIEnv *env, jclass unused, jint state) {
458-
ProfiledThread *current = ProfiledThread::initCurrentThreadSignalSafe();
459-
if (current == nullptr) {
484+
JNIEnv *env, jclass unused, jthread thread, jint state) {
485+
OSThreadState decoded;
486+
if (!decodeJavaBlockState(state, decoded) ||
487+
!JVMSupport::isPlatformThread(env, thread)) {
460488
return 0;
461489
}
462-
OSThreadState decoded;
463-
if (!decodeJavaBlockState(state, decoded)) {
490+
ProfiledThread *current = ProfiledThread::initCurrentThreadSignalSafe();
491+
if (current == nullptr) {
464492
return 0;
465493
}
466494
u64 span_id = 0, root_span_id = 0;
@@ -480,9 +508,9 @@ Java_com_datadoghq_profiler_JavaProfiler_blockEnter0(
480508

481509
extern "C" DLLEXPORT void JNICALL
482510
Java_com_datadoghq_profiler_JavaProfiler_blockExit0(
483-
JNIEnv *env, jclass unused, jlong token) {
511+
JNIEnv *env, jclass unused, jthread thread, jlong token) {
484512
u64 block_token = static_cast<u64>(token);
485-
if (block_token == 0) {
513+
if (block_token == 0 || !JVMSupport::isPlatformThread(env, thread)) {
486514
return;
487515
}
488516

ddprof-lib/src/main/cpp/jvmSupport.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
#include <jni.h>
2020

21+
#include <atomic>
22+
2123
using JniFunction = void (JNICALL*)();
2224
using IsVirtualThreadFunction = jboolean (JNICALL*)(JNIEnv*, jobject);
2325

@@ -44,11 +46,21 @@ bool JVMSupport::isPlatformThread(JNIEnv* jni, jthread thread) {
4446

4547
const JniFunction* functions =
4648
reinterpret_cast<const JniFunction*>(jni->functions);
49+
if (functions == nullptr) return false;
4750
IsVirtualThreadFunction is_virtual_thread =
4851
reinterpret_cast<IsVirtualThreadFunction>(
4952
functions[IS_VIRTUAL_THREAD_INDEX]);
50-
return is_virtual_thread != nullptr &&
51-
is_virtual_thread(jni, thread) == JNI_FALSE;
53+
if (is_virtual_thread == nullptr) {
54+
static std::atomic<bool> warning_emitted{false};
55+
bool expected = false;
56+
if (warning_emitted.compare_exchange_strong(expected, true,
57+
std::memory_order_relaxed)) {
58+
LOG_WARN("JNI version 19 or later does not expose IsVirtualThread; "
59+
"JVM producer callbacks will be ignored");
60+
}
61+
return false;
62+
}
63+
return is_virtual_thread(jni, thread) == JNI_FALSE;
5264
}
5365

5466
bool JVMSupport::initialize() {

ddprof-lib/src/main/cpp/profiler.cpp

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1541,6 +1541,26 @@ Error Profiler::init() {
15411541
return Error::OK;
15421542
}
15431543

1544+
void Profiler::setTaskBlockEnabled(bool enabled) {
1545+
if (enabled) {
1546+
// Keep callback admission closed until native setup has either completed
1547+
// or rolled back, so partial event enablement cannot create paired state.
1548+
bool monitor_events_enabled =
1549+
VM::nativeMonitorEventsAvailable() &&
1550+
VM::setNativeMonitorEventsEnabled(true);
1551+
_task_block_monitor_events_enabled.store(monitor_events_enabled,
1552+
std::memory_order_release);
1553+
_task_block_enabled.store(true, std::memory_order_release);
1554+
return;
1555+
}
1556+
1557+
_task_block_enabled.store(false, std::memory_order_release);
1558+
if (_task_block_monitor_events_enabled.exchange(
1559+
false, std::memory_order_acq_rel)) {
1560+
VM::setNativeMonitorEventsEnabled(false);
1561+
}
1562+
}
1563+
15441564
Error Profiler::start(Arguments &args, bool reset) {
15451565
MutexLocker ml(_state_lock);
15461566
Error error = checkState();
@@ -1876,9 +1896,8 @@ Error Profiler::start(Arguments &args, bool reset) {
18761896
// Paired with drainInflight() on the stop side.
18771897
_cpu_engine->enableEvents(true);
18781898

1879-
_task_block_enabled.store(
1880-
(activated & EM_WALL) && args._wall_precheck && track_unfiltered_wall,
1881-
std::memory_order_release);
1899+
setTaskBlockEnabled(
1900+
(activated & EM_WALL) && args._wall_precheck && track_unfiltered_wall);
18821901
_state.store(RUNNING, std::memory_order_release);
18831902
_start_time = time(NULL);
18841903
__atomic_add_fetch(&_epoch, 1, __ATOMIC_RELAXED);
@@ -1903,7 +1922,7 @@ Error Profiler::stop() {
19031922
if (state() != RUNNING) {
19041923
return Error("Profiler is not active");
19051924
}
1906-
_task_block_enabled.store(false, std::memory_order_release);
1925+
setTaskBlockEnabled(false);
19071926

19081927
// Order matters: disable engines first so the _enabled check inside signal
19091928
// handlers will fail for any new signal delivered from now on. drain() then
@@ -2090,7 +2109,9 @@ Error Profiler::dump(const char *path, const int length) {
20902109
// rotateDictsAndRun rotates the dictionaries, takes lockAll() around the
20912110
// dump (fences ASGCT/JNI writers to CallTraceStorage), then clearStandby()s
20922111
// the rotated buffers. StringDictionary's RefCountGuard protocol handles
2093-
// its own writer/reader coordination.
2112+
// its own writer/reader coordination; #527's classMapSharedGuard readers
2113+
// (deferred vtable receiver resolution) are coordinated through
2114+
// _class_map_lock.
20942115
if (beginTaskBlockRotation()) {
20952116
rotateDictsAndRun([&]{
20962117
err = _jfr.dump(path, length);

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ class alignas(alignof(SpinLock)) Profiler {
133133
alignas(DEFAULT_CACHE_LINE_SIZE) u64 _failures[ASGCT_FAILURE_TYPES];
134134
bool _wall_precheck = false;
135135
std::atomic<bool> _task_block_enabled{false};
136+
std::atomic<bool> _task_block_monitor_events_enabled{false};
136137
std::atomic<bool> _task_block_rotation{false};
137138
std::atomic<u64> _task_block_inflight{0};
138139

@@ -185,6 +186,7 @@ class alignas(alignof(SpinLock)) Profiler {
185186

186187
void lockAll();
187188
void unlockAll();
189+
void setTaskBlockEnabled(bool enabled);
188190
bool beginTaskBlockRotation();
189191
void endTaskBlockRotation();
190192

@@ -494,6 +496,9 @@ class alignas(alignof(SpinLock)) Profiler {
494496
bool taskBlockEnabled() const {
495497
return _task_block_enabled.load(std::memory_order_acquire);
496498
}
499+
bool nativeMonitorTaskBlockEnabled() const {
500+
return _task_block_monitor_events_enabled.load(std::memory_order_acquire);
501+
}
497502
void writeLog(LogLevel level, const char *message);
498503
void writeLog(LogLevel level, const char *message, size_t len);
499504
void writeDatadogProfilerSetting(int tid, int length, const char *name,

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ enum class BlockRunOwner : int {
4040

4141
struct BlockRunSnapshot {
4242
OSThreadState active_state{OSThreadState::UNKNOWN};
43+
BlockRunOwner owner{BlockRunOwner::NONE};
44+
u64 generation{0};
45+
bool active{false};
4346
bool context_eligible{false};
4447
};
4548

@@ -285,6 +288,10 @@ class ThreadFilter {
285288
inline BlockRunSnapshot snapshotBlockRun() const {
286289
BlockRunSnapshot snapshot;
287290
snapshot.active_state = activeBlockState();
291+
snapshot.owner = activeBlockOwner();
292+
snapshot.generation = blockGeneration();
293+
snapshot.active = snapshot.owner != BlockRunOwner::NONE &&
294+
snapshot.active_state != OSThreadState::UNKNOWN;
288295
snapshot.context_eligible = activeBlockRemainedOutsideContextWindow();
289296
return snapshot;
290297
}

0 commit comments

Comments
 (0)