Skip to content

Commit c133852

Browse files
committed
fix: preserve TaskBlock stack references across JFR dumps
1 parent ef28ce3 commit c133852

13 files changed

Lines changed: 307 additions & 44 deletions

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
"task_block_skipped_no_stack_reference") \
128128
X(TASK_BLOCK_RECORD_FAILED, "task_block_record_failed") \
129129
X(TASK_BLOCK_QUEUE_DROPPED, "task_block_queue_dropped") \
130+
X(TASK_BLOCK_DROPPED_ROTATION, "task_block_dropped_rotation") \
130131
X(JVMTI_STACKS_DROPPED_LOCK, "jvmti_stacks_dropped_lock") \
131132
X(SAMPLES_DROPPED_REC_LOCK, "samples_dropped_rec_lock") \
132133
X(SAMPLES_DROPPED_THREAD_LOCAL, "samples_dropped_thread_local")

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

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -360,20 +360,31 @@ Java_com_datadoghq_profiler_JavaProfiler_parkExit0(
360360
return;
361361
}
362362

363-
int tid = ProfiledThread::currentTid();
364-
if (tid >= 0) {
365-
recordTaskBlockWithContextIfEligible(tid, start_ticks, TSC::ticks(),
366-
park_context, (u64)blocker,
367-
(u64)unblockingSpanId);
363+
Profiler* profiler = Profiler::instance();
364+
bool activity = profiler->tryEnterTaskBlockActivity();
365+
if (!activity) {
366+
profiler->waitForTaskBlockRotation();
368367
}
369-
370-
ThreadFilter *tf = Profiler::instance()->threadFilter();
371-
if (tf->enabled()) {
372-
ThreadFilter::SlotID slot_id = ThreadFilter::tokenSlotId(park_block_token);
373-
if (current->filterSlotId() == slot_id) {
374-
tf->exitBlockedRun(slot_id, ThreadFilter::tokenGeneration(park_block_token));
375-
}
368+
ThreadFilter* tf = profiler->threadFilter();
369+
ThreadFilter::SlotID slot_id = ThreadFilter::tokenSlotId(park_block_token);
370+
BlockRunSnapshot snapshot{};
371+
bool exited = tf->enabled() && current->filterSlotId() == slot_id &&
372+
tf->snapshotAndExitBlockedRun(
373+
slot_id, ThreadFilter::tokenGeneration(park_block_token), &snapshot);
374+
if (!activity) {
375+
Counters::increment(TASK_BLOCK_DROPPED_ROTATION);
376+
return;
376377
}
378+
if (exited) {
379+
int tid = ProfiledThread::currentTid();
380+
OSThreadState observed_state = snapshot.sampled_state != OSThreadState::UNKNOWN
381+
? snapshot.sampled_state : snapshot.active_state;
382+
recordTaskBlockWithStackReferenceIfEligible(
383+
tid, start_ticks, TSC::ticks(), park_context, (u64)blocker,
384+
(u64)unblockingSpanId, snapshot.call_trace_id,
385+
snapshot.correlation_id, observed_state, true);
386+
}
387+
profiler->leaveTaskBlockActivity();
377388
}
378389

379390
static bool decodeJavaBlockState(jint state, OSThreadState &decoded) {

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,28 @@ void NativeBlockScope::finish(u64 end_ticks) {
104104
}
105105
_active = false;
106106

107-
ThreadFilter* thread_filter = Profiler::instance()->threadFilter();
107+
Profiler* profiler = Profiler::instance();
108+
bool activity = profiler->tryEnterTaskBlockActivity();
109+
if (!activity) {
110+
profiler->waitForTaskBlockRotation();
111+
}
112+
ThreadFilter* thread_filter = profiler->threadFilter();
108113
BlockRunSnapshot snapshot{};
109114
snapshot.active_state = _state;
110115
snapshot.sampled_state = OSThreadState::UNKNOWN;
111116
snapshot.owner = BlockRunOwner::NATIVE;
112117
if (!thread_filter->enabled() ||
113118
!thread_filter->snapshotAndExitBlockedRun(_slot_id, _generation, &snapshot)) {
119+
if (activity) {
120+
profiler->leaveTaskBlockActivity();
121+
} else {
122+
Counters::increment(TASK_BLOCK_DROPPED_ROTATION);
123+
}
124+
return;
125+
}
126+
127+
if (!activity) {
128+
Counters::increment(TASK_BLOCK_DROPPED_ROTATION);
114129
return;
115130
}
116131

@@ -121,7 +136,8 @@ void NativeBlockScope::finish(u64 end_ticks) {
121136
_tid, _start_ticks, end_ticks, _context, _blocker, 0,
122137
snapshot.has_stack_reference ? snapshot.call_trace_id : 0,
123138
snapshot.has_stack_reference ? snapshot.correlation_id : 0,
124-
observed_state);
139+
observed_state, true);
140+
profiler->leaveTaskBlockActivity();
125141
}
126142

127143
#endif // __linux__

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

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
#include <memory>
4848
#include <set>
4949
#include <string>
50+
#include <thread>
5051
#include <vector>
5152
#include <signal.h>
5253
#include <stdint.h>
@@ -162,18 +163,58 @@ void Profiler::stopTaskBlockDrain() {
162163
drainTaskBlockQueue(true);
163164
}
164165

165-
void Profiler::drainTaskBlockQueue(bool record) {
166+
void Profiler::drainTaskBlockQueue(bool record, bool rotation_owner) {
167+
if (!rotation_owner && !tryEnterTaskBlockActivity()) {
168+
return;
169+
}
166170
QueuedTaskBlockEvent queued;
167171
u64 generation = _task_block_generation.load(std::memory_order_acquire);
168172
while (_task_block_queue.tryPop(queued)) {
169173
if (record && queued.generation == generation) {
170-
if (recordTaskBlockLive(queued.tid, &queued.event)) {
174+
if (recordTaskBlockLiveUnchecked(queued.tid, &queued.event)) {
171175
Counters::increment(TASK_BLOCK_EMITTED);
172176
} else {
173177
Counters::increment(TASK_BLOCK_RECORD_FAILED);
174178
}
175179
}
176180
}
181+
if (!rotation_owner) {
182+
leaveTaskBlockActivity();
183+
}
184+
}
185+
186+
bool Profiler::tryEnterTaskBlockActivity() {
187+
if (_task_block_rotation.load(std::memory_order_acquire)) {
188+
return false;
189+
}
190+
_task_block_inflight.fetch_add(1, std::memory_order_acq_rel);
191+
if (_task_block_rotation.load(std::memory_order_acquire)) {
192+
_task_block_inflight.fetch_sub(1, std::memory_order_acq_rel);
193+
return false;
194+
}
195+
return true;
196+
}
197+
198+
void Profiler::leaveTaskBlockActivity() {
199+
_task_block_inflight.fetch_sub(1, std::memory_order_release);
200+
}
201+
202+
void Profiler::waitForTaskBlockRotation() {
203+
while (_task_block_rotation.load(std::memory_order_acquire)) {
204+
std::this_thread::yield();
205+
}
206+
}
207+
208+
void Profiler::beginTaskBlockRotation() {
209+
_task_block_rotation.store(true, std::memory_order_release);
210+
while (_task_block_inflight.load(std::memory_order_acquire) != 0) {
211+
std::this_thread::yield();
212+
}
213+
drainTaskBlockQueue(true, true);
214+
}
215+
216+
void Profiler::endTaskBlockRotation() {
217+
_task_block_rotation.store(false, std::memory_order_release);
177218
}
178219

179220
void Profiler::onThreadStart(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread) {
@@ -679,7 +720,9 @@ void Profiler::recordDeferredSample(int tid, u64 call_trace_id, jint event_type,
679720

680721
bool Profiler::recordSample(void *ucontext, u64 counter, int tid,
681722
jint event_type, u64 call_trace_id, Event *event,
682-
u64 *recorded_call_trace_id) {
723+
u64 *recorded_call_trace_id,
724+
ThreadFilter::SlotID task_block_slot_id,
725+
OSThreadState task_block_state) {
683726
atomicIncRelaxed(_total_samples);
684727

685728
u32 lock_index = getLockIndex(tid);
@@ -748,14 +791,20 @@ bool Profiler::recordSample(void *ucontext, u64 counter, int tid,
748791
if (recorded && recorded_call_trace_id != nullptr) {
749792
*recorded_call_trace_id = call_trace_id;
750793
}
794+
if (recorded && task_block_slot_id >= 0) {
795+
_thread_filter.markTaskBlockSampled(task_block_slot_id, task_block_state,
796+
call_trace_id, 0);
797+
}
751798

752799
_locks[lock_index].unlock();
753800
return recorded;
754801
}
755802

756803
bool Profiler::recordSampleDelegated(void *ucontext, u64 weight, int tid,
757804
jint event_type, Event *event,
758-
u64 *recorded_correlation_id) {
805+
u64 *recorded_correlation_id,
806+
ThreadFilter::SlotID task_block_slot_id,
807+
OSThreadState task_block_state) {
759808
if (!VM::canRequestStackTrace()) {
760809
return false;
761810
}
@@ -793,6 +842,10 @@ bool Profiler::recordSampleDelegated(void *ucontext, u64 weight, int tid,
793842
if (recorded && recorded_correlation_id != nullptr) {
794843
*recorded_correlation_id = correlation_id;
795844
}
845+
if (recorded && task_block_slot_id >= 0) {
846+
_thread_filter.markTaskBlockSampled(task_block_slot_id, task_block_state,
847+
0, correlation_id);
848+
}
796849
_locks[lock_index].unlock();
797850
return recorded;
798851
}
@@ -830,7 +883,7 @@ void Profiler::recordQueueTime(int tid, QueueTimeEvent *event) {
830883
_locks[lock_index].unlock();
831884
}
832885

833-
bool Profiler::recordTaskBlockLive(int tid, TaskBlockEvent *event) {
886+
bool Profiler::recordTaskBlockLiveUnchecked(int tid, TaskBlockEvent *event) {
834887
#ifdef UNIT_TEST
835888
record_task_block_live_calls_for_test.fetch_add(1, std::memory_order_acq_rel);
836889
last_task_block_tid_for_test.store(tid, std::memory_order_release);
@@ -852,6 +905,10 @@ bool Profiler::recordTaskBlockLive(int tid, TaskBlockEvent *event) {
852905
return recorded;
853906
}
854907

908+
bool Profiler::recordTaskBlockLive(int tid, TaskBlockEvent *event) {
909+
return recordTaskBlockLiveUnchecked(tid, event);
910+
}
911+
855912
bool Profiler::recordTaskBlockAsync(int tid, TaskBlockEvent *event) {
856913
if (!_task_block_drain_running.load(std::memory_order_acquire)) {
857914
Counters::increment(TASK_BLOCK_QUEUE_DROPPED);
@@ -1858,10 +1915,12 @@ Error Profiler::dump(const char *path, const int length) {
18581915
// its own writer/reader coordination; #527's classMapSharedGuard readers
18591916
// (deferred vtable receiver resolution) are coordinated through
18601917
// _class_map_lock.
1918+
beginTaskBlockRotation();
18611919
rotateDictsAndRun([&]{
18621920
err = _jfr.dump(path, length);
18631921
__atomic_add_fetch(&_epoch, 1, __ATOMIC_SEQ_CST);
18641922
});
1923+
endTaskBlockRotation();
18651924

18661925
_thread_info.clearAll(thread_ids);
18671926
_thread_info.reportCounters();

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

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ class alignas(alignof(SpinLock)) Profiler {
127127
pthread_t _task_block_drain_thread;
128128
std::atomic<bool> _task_block_drain_running;
129129
std::atomic<u64> _task_block_generation;
130+
// Dump closes this gate, waits for active producers/consumers, then drains the
131+
// queue before rotating call-trace storage.
132+
std::atomic<bool> _task_block_rotation;
133+
std::atomic<u32> _task_block_inflight;
130134

131135
// dlopen() hook support
132136
void **_dlopen_entry;
@@ -141,7 +145,10 @@ class alignas(alignof(SpinLock)) Profiler {
141145
static void *taskBlockDrainLoop(void *arg);
142146
void startTaskBlockDrain();
143147
void stopTaskBlockDrain();
144-
void drainTaskBlockQueue(bool record);
148+
void drainTaskBlockQueue(bool record, bool rotation_owner = false);
149+
bool recordTaskBlockLiveUnchecked(int tid, TaskBlockEvent *event);
150+
void beginTaskBlockRotation();
151+
void endTaskBlockRotation();
145152

146153
u32 getLockIndex(int tid);
147154
int getNativeTrace(void *ucontext, ASGCT_CallFrame *frames, int event_type,
@@ -208,11 +215,16 @@ class alignas(alignof(SpinLock)) Profiler {
208215
_num_context_attributes(0), _omit_stacktraces(false),
209216
_remote_symbolication(false), _task_block_drain_thread(),
210217
_task_block_drain_running(false), _task_block_generation(0),
218+
_task_block_rotation(false), _task_block_inflight(0),
211219
_dlopen_entry(NULL) {
212220

213221
for (int i = 0; i < CONCURRENCY_LEVEL; i++) {
214222
_calltrace_buffer[i] = NULL;
215223
}
224+
_call_trace_storage.registerLivenessChecker(
225+
[this](std::unordered_set<u64>& trace_ids) {
226+
_thread_filter.collectTaskBlockLiveTraceIds(trace_ids);
227+
});
216228
}
217229

218230
static inline Profiler *instance() {
@@ -397,15 +409,19 @@ class alignas(alignof(SpinLock)) Profiler {
397409
ASGCT_CallFrame *frames, int lock_index);
398410
bool recordSample(void *ucontext, u64 weight, int tid, jint event_type,
399411
u64 call_trace_id, Event *event,
400-
u64 *recorded_call_trace_id = nullptr);
412+
u64 *recorded_call_trace_id = nullptr,
413+
ThreadFilter::SlotID task_block_slot_id = -1,
414+
OSThreadState task_block_state = OSThreadState::UNKNOWN);
401415
// Delegated sample path: stack-walking is performed by the HotSpot JFR
402416
// RequestStackTrace extension (the JVM emits the stack trace into its own
403417
// JFR recording). We only emit the CPU/wall sample event with no
404418
// stack-trace reference, tagged by the correlation ID we passed to
405419
// RequestStackTrace as user_data.
406420
bool recordSampleDelegated(void *ucontext, u64 weight, int tid,
407421
jint event_type, Event *event,
408-
u64 *recorded_correlation_id = nullptr);
422+
u64 *recorded_correlation_id = nullptr,
423+
ThreadFilter::SlotID task_block_slot_id = -1,
424+
OSThreadState task_block_state = OSThreadState::UNKNOWN);
409425
u64 recordJVMTISample(u64 weight, int tid, jthread thread, jint event_type, Event *event, bool deferred);
410426
void recordDeferredSample(int tid, u64 call_trace_id, jint event_type, Event *event);
411427
void recordExternalSample(u64 weight, int tid, int num_frames,
@@ -416,6 +432,11 @@ class alignas(alignof(SpinLock)) Profiler {
416432
void recordQueueTime(int tid, QueueTimeEvent *event);
417433
bool recordTaskBlockLive(int tid, TaskBlockEvent *event);
418434
bool recordTaskBlockAsync(int tid, TaskBlockEvent *event);
435+
// TaskBlock producers must hold an activity across stack-reference snapshot
436+
// and event recording/enqueue so dump cannot rotate between those operations.
437+
bool tryEnterTaskBlockActivity();
438+
void leaveTaskBlockActivity();
439+
void waitForTaskBlockRotation();
419440
bool taskBlockAsyncActive() const {
420441
return _task_block_drain_running.load(std::memory_order_acquire);
421442
}
@@ -474,6 +495,14 @@ class alignas(alignof(SpinLock)) Profiler {
474495
drainTaskBlockQueue(record);
475496
}
476497

498+
void beginTaskBlockRotationForTest() {
499+
beginTaskBlockRotation();
500+
}
501+
502+
void endTaskBlockRotationForTest() {
503+
endTaskBlockRotation();
504+
}
505+
477506
void startTaskBlockDrainForTest() {
478507
startTaskBlockDrain();
479508
}

0 commit comments

Comments
 (0)