Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
14 changes: 8 additions & 6 deletions .github/scripts/generate-test-summary.sh
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ while IFS= read -r job; do

# Only process test jobs (match pattern: test-linux-{libc}-{arch} ({java}, {config}))
# Note: regex stored in variable to avoid bash parsing issues with ) character
test_job_pattern='^test-linux-([a-z]+)-([a-z0-9]+) \(([^,]+), ([^)]+)\)$'
# Note: No ^ anchor because reusable workflow jobs are prefixed with caller job name
# e.g., "test-matrix / test-linux-glibc-amd64 (8, debug)"
test_job_pattern='test-linux-([a-z]+)-([a-z0-9]+) \(([^,]+), ([^)]+)\)$'
if [[ "$name" =~ $test_job_pattern ]]; then
libc="${BASH_REMATCH[1]}"
arch="${BASH_REMATCH[2]}"
Expand Down Expand Up @@ -158,10 +160,10 @@ for key in "${!job_status[@]}"; do
dur="${job_duration[$key]:-0}"

case "$status" in
success) ((passed_jobs++)) ;;
success) ((++passed_jobs)) ;;
failure) ;; # already counted
skipped) ((skipped_jobs++)) ;;
cancelled) ((cancelled_jobs++)) ;;
skipped) ((++skipped_jobs)) ;;
cancelled) ((++cancelled_jobs)) ;;
esac

((total_duration += dur)) || true
Expand Down Expand Up @@ -255,9 +257,9 @@ log "Generating markdown summary..."
echo ""

# Separator row
printf "|----------|"
printf "%s" "|----------|"
for _ in "${sorted_java[@]}"; do
printf "--------|"
printf "%s" "--------|"
done
echo ""

Expand Down
2 changes: 1 addition & 1 deletion ddprof-lib/src/main/cpp/callTraceStorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#include "thread.h"
#include "vmEntry.h" // For BCI_ERROR constant
#include "arch.h" // For LP64_ONLY macro and COMMA macro
#include "criticalSection.h" // For table swap critical sections
#include "guards.h" // For table swap critical sections
#include "primeProbing.h"
#include "thread.h"
#include <string.h>
Expand Down
12 changes: 10 additions & 2 deletions ddprof-lib/src/main/cpp/context.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021, 2022 Datadog, Inc
* Copyright 2021, 2026 Datadog, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,16 +17,24 @@
#include "context.h"
#include "counters.h"
#include "os.h"
#include "guards.h"
#include "thread.h"
#include <cstring>

DLLEXPORT thread_local Context context_tls_v1;

Context& Contexts::initializeContextTls() {
// Block profiling signals during context_tls_v1 first access to prevent
// signal handler from interrupting musl's TLS initialization
SignalBlocker blocker;

// FIRST access to context_tls_v1 - triggers musl TLS init on first call
// ProfiledThread::current() will never return nullptr
Context& ctx = context_tls_v1;
// Store pointer for signal-safe access
// Store pointer in ProfiledThread for signal-safe access
ProfiledThread::current()->markContextTlsInitialized(&ctx);

// Signal mask automatically restored when blocker goes out of scope
return ctx;
}

Expand Down
3 changes: 3 additions & 0 deletions ddprof-lib/src/main/cpp/ctimer.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ class CTimer : public Engine {
inline void enableEvents(bool enabled) {
__atomic_store_n(&_enabled, enabled, __ATOMIC_RELEASE);
}

// Get the signal number used by CTimer (0 if not initialized)
static int getSignal() { return _signal; }
};

#else
Expand Down
2 changes: 1 addition & 1 deletion ddprof-lib/src/main/cpp/ctimer_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

#ifdef __linux__

#include "criticalSection.h"
#include "guards.h"
#include "ctimer.h"
#include "debugSupport.h"
#include "libraries.h"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
/*
* Copyright 2025, Datadog, Inc.
* SPDX-License-Identifier: Apache-2.0
* Copyright 2025, 2026 Datadog, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "criticalSection.h"
#include "guards.h"
#include "common.h"
#include "os.h"
#include "thread.h"
Expand All @@ -27,7 +38,8 @@ CriticalSection::CriticalSection() : _entered(false), _using_fallback(false), _w
uint32_t bit_index = tid % 64;
_bit_mask = 1ULL << bit_index;

uint64_t old_word = __atomic_fetch_or(&_fallback_bitmap[_word_index], _bit_mask, __ATOMIC_RELAXED);
// Use ACQUIRE ordering to ensure visibility of protected data after acquiring critical section
uint64_t old_word = __atomic_fetch_or(&_fallback_bitmap[_word_index], _bit_mask, __ATOMIC_ACQUIRE);
_entered = !(old_word & _bit_mask); // Success if bit was previously 0
}
}
Expand All @@ -36,7 +48,8 @@ CriticalSection::~CriticalSection() {
if (_entered) {
if (_using_fallback) {
// Clear the bit atomically for fallback bitmap
__atomic_fetch_and(&_fallback_bitmap[_word_index], ~_bit_mask, __ATOMIC_RELAXED);
// Use RELEASE ordering to ensure protected data writes are visible before releasing
__atomic_fetch_and(&_fallback_bitmap[_word_index], ~_bit_mask, __ATOMIC_RELEASE);
} else {
// Release ProfiledThread flag
ProfiledThread* current = ProfiledThread::currentSignalSafe();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,44 @@
/*
* Copyright 2025, Datadog, Inc.
* SPDX-License-Identifier: Apache-2.0
* Copyright 2025, 2026 Datadog, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifndef _CRITICALSECTION_H
#define _CRITICALSECTION_H
#ifndef _GUARDS_H
#define _GUARDS_H

#include <cstdint>
#include <cstddef>
#include <signal.h>
#include <pthread.h>

/**
* Race-free critical section using atomic compare-and-swap.
*
*
* Hybrid implementation:
* - Primary: Uses ProfiledThread storage when available (zero memory overhead)
* - Fallback: Hash-based bitmap for stress tests and cases without ProfiledThread
*
* This approach is async-signal-safe and avoids TLS allocation issues.
*
*
* Usage:
* {
* CriticalSection cs; // Atomically claim critical section
* if (!cs.entered()) return; // Another thread/signal handler is active
* // Complex data structure operations
* // Signal handlers will be blocked from entering
* } // Critical section automatically released
*
*
* This eliminates race conditions between signal handlers and normal code
* by ensuring only one can hold the critical section at a time per thread.
*
Expand Down Expand Up @@ -54,17 +67,17 @@ class CriticalSection {
bool _using_fallback; // Track which storage mechanism we're using
uint32_t _word_index; // For fallback bitmap cleanup
uint64_t _bit_mask; // For fallback bitmap cleanup

public:
CriticalSection();
~CriticalSection();

// Non-copyable, non-movable
CriticalSection(const CriticalSection&) = delete;
CriticalSection& operator=(const CriticalSection&) = delete;
CriticalSection(CriticalSection&&) = delete;
CriticalSection& operator=(CriticalSection&&) = delete;

// Check if this instance successfully entered the critical section
bool entered() const { return _entered; }

Expand All @@ -73,4 +86,66 @@ class CriticalSection {
static uint32_t hash_tid(int tid);
};

#endif // _CRITICALSECTION_H
/**
* RAII guard to block profiling signals during critical operations.
*
* Blocks SIGPROF and SIGVTALRM signals on construction and automatically
* restores the original signal mask on destruction. This prevents signal
* handlers from interrupting operations that are not async-signal-safe,
* such as musl libc's TLS initialization.
*
* !WARNING!
* For guarding access to code running as a signal handler use CriticalSection
* !WARNING!
*
* Usage:
* {
* SignalBlocker blocker; // Blocks profiling signals
* // Perform operations that must not be interrupted by signals
* // (e.g., TLS initialization, malloc, etc.)
* } // Signal mask automatically restored
*
* The blocker is exception-safe: the signal mask will be restored even
* if an exception is thrown within the protected scope.
*
* Note: This only blocks signals for the current thread. Other threads
* continue to receive profiling signals normally.
*/
class SignalBlocker {
private:
sigset_t _old_mask;
bool _active;

public:
SignalBlocker() : _active(false) {
sigset_t prof_signals;
sigemptyset(&prof_signals);

// Add all profiling signals that could interrupt us
sigaddset(&prof_signals, SIGPROF); // Used by ITimer and CTimer
sigaddset(&prof_signals, SIGVTALRM); // Used by WallClock
#ifdef __linux__
// Block RT signals used by TLS priming and potentially other profilers (Linux-only)
// This prevents any RT signal handler from interrupting TLS initialization
for (int sig = SIGRTMIN; sig <= SIGRTMIN + 5 && sig <= SIGRTMAX; sig++) {
sigaddset(&prof_signals, sig);
}
#endif

if (pthread_sigmask(SIG_BLOCK, &prof_signals, &_old_mask) == 0) {
_active = true;
}
}

~SignalBlocker() {
if (_active) {
pthread_sigmask(SIG_SETMASK, &_old_mask, nullptr);
}
}

// Non-copyable
SignalBlocker(const SignalBlocker&) = delete;
SignalBlocker& operator=(const SignalBlocker&) = delete;
};

#endif // _GUARDS_H
2 changes: 1 addition & 1 deletion ddprof-lib/src/main/cpp/itimer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
#include "thread.h"
#include "threadState.inline.h"
#include "vmStructs.h"
#include "criticalSection.h"
#include "guards.h"
#include <sys/time.h>

volatile bool ITimer::_enabled = false;
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 @@ -20,7 +20,7 @@
#include "arch.h"
#include "arguments.h"
#include "context.h"
#include "criticalSection.h"
#include "guards.h"
#include "debugSupport.h"
#include "libraries.h"
#include "log.h"
Expand Down
2 changes: 1 addition & 1 deletion ddprof-lib/src/main/cpp/profiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#include "profiler.h"
#include "asyncSampleMutex.h"
#include "context.h"
#include "criticalSection.h"
#include "guards.h"
#include "common.h"
#include "counters.h"
#include "ctimer.h"
Expand Down
2 changes: 1 addition & 1 deletion ddprof-lib/src/main/cpp/wallClock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
#include "thread.h"
#include "threadState.inline.h"
#include "vmStructs.h"
#include "criticalSection.h"
#include "guards.h"
#include <math.h>
#include <random>
#include <algorithm> // For std::sort and std::binary_search
Expand Down
2 changes: 1 addition & 1 deletion ddprof-lib/src/test/cpp/stress_callTraceStorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include "gtest/gtest.h"
#include "callTraceStorage.h"
#include "callTraceHashTable.h"
#include "criticalSection.h"
#include "guards.h"
#include <vector>
#include <unordered_set>
#include <thread>
Expand Down
Loading