From 8d2b6372120126f39746258fce7f6445ee0ea793 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 17 Jun 2026 13:36:20 +0000 Subject: [PATCH 01/18] v0 --- ddprof-lib/src/main/cpp/threadLocal.h | 74 +++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 ddprof-lib/src/main/cpp/threadLocal.h diff --git a/ddprof-lib/src/main/cpp/threadLocal.h b/ddprof-lib/src/main/cpp/threadLocal.h new file mode 100644 index 000000000..d051b0356 --- /dev/null +++ b/ddprof-lib/src/main/cpp/threadLocal.h @@ -0,0 +1,74 @@ +/* + * Copyright 2026, Datadog, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __TLS_H +#define __TLS_H + +#include +#include +#include +#include "arch.h" + +typedef void (*CLEAN_FUNC)(void*); + +template +class ThreadLocal { +protected: + static inline pthread_key_t _key; + static inline pthread_once_t _key_once = PTHREAD_ONCE_INIT; + + static void create_thread_key() { + pthread_key_create(&_key, F); + } +public: + ThreadLocal() { + pthread_once(&_key_once, create_thread_key);; + } + ~ThreadLocal() { + pthread_key_delete(_key); + } + static void set(T value) { + pthread_setspecific(_key, (const void*)value); + } + + static T get() { + return (T)pthread_getspecific(_key); + } +}; + +template <> +class ThreadLocal { +protected: + static inline pthread_key_t _key; + static inline pthread_once_t _key_once = PTHREAD_ONCE_INIT; + + static void create_thread_key() { + pthread_key_create(&_key, nullptr); + } +public: + ThreadLocal() { + pthread_once(&_key_once, create_thread_key); + } + ~ThreadLocal() { + pthread_key_delete(_key); + } + + // double <--> u64 cast, preserve bit format + // Can use std::bit_cast after upgrade C++ version to 20 + static void set(double value) { + u64 val; + memcpy(&val, &value, sizeof(value)); + pthread_setspecific(_key, (const void*)val); + } + + static double get() { + u64 val = (u64)pthread_getspecific(_key); + double value; + memcpy(&value, &val, sizeof(val)); + return value; + } +}; + +#endif // __TLS_H From 02614cfd36b7333014c3e4942b66f972a6317c7a Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 17 Jun 2026 14:09:52 +0000 Subject: [PATCH 02/18] v1 --- ddprof-lib/src/main/cpp/livenessTracker.cpp | 9 +++++---- ddprof-lib/src/main/cpp/threadLocal.h | 8 ++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/ddprof-lib/src/main/cpp/livenessTracker.cpp b/ddprof-lib/src/main/cpp/livenessTracker.cpp index 840f6c606..dbfc53e88 100644 --- a/ddprof-lib/src/main/cpp/livenessTracker.cpp +++ b/ddprof-lib/src/main/cpp/livenessTracker.cpp @@ -19,6 +19,7 @@ #include "os.h" #include "profiler.h" #include "thread.h" +#include "threadLocal.h" #include "tsc.h" #include #include @@ -289,10 +290,10 @@ void LivenessTracker::track(JNIEnv *env, AllocEvent &event, jint tid, static thread_local std::mt19937 gen(std::random_device{}()); static thread_local std::uniform_real_distribution<> dis(0, 1.0); - static thread_local double skipped = 0; + static ThreadLocal skipped; if (_subsample_ratio < 1.0 && dis(gen) > _subsample_ratio) { - skipped += static_cast(event._weight) * event._size; + skipped.set(skipped.get() + static_cast(event._weight) * event._size); return; } @@ -322,7 +323,7 @@ void LivenessTracker::track(JNIEnv *env, AllocEvent &event, jint tid, _table[idx].time = TSC::ticks(); _table[idx].ref = ref; _table[idx].alloc = event; - _table[idx].skipped = skipped; + _table[idx].skipped = skipped.get(); _table[idx].age = 0; _table[idx].call_trace_id = call_trace_id; _table[idx].ctx = ContextApi::snapshot(); @@ -376,7 +377,7 @@ void LivenessTracker::track(JNIEnv *env, AllocEvent &event, jint tid, env->DeleteWeakGlobalRef(ref); } } - skipped = 0; // reset the subsampling skipped bytes + skipped.set(0); // reset the subsampling skipped bytes } void JNICALL LivenessTracker::GarbageCollectionFinish(jvmtiEnv *jvmti_env) { diff --git a/ddprof-lib/src/main/cpp/threadLocal.h b/ddprof-lib/src/main/cpp/threadLocal.h index d051b0356..05481731f 100644 --- a/ddprof-lib/src/main/cpp/threadLocal.h +++ b/ddprof-lib/src/main/cpp/threadLocal.h @@ -3,8 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -#ifndef __TLS_H -#define __TLS_H +#ifndef __THREADLOCAL_H +#define __THREADLOCAL_H #include #include @@ -12,7 +12,6 @@ #include "arch.h" typedef void (*CLEAN_FUNC)(void*); - template class ThreadLocal { protected: @@ -51,6 +50,7 @@ class ThreadLocal { ThreadLocal() { pthread_once(&_key_once, create_thread_key); } + ~ThreadLocal() { pthread_key_delete(_key); } @@ -71,4 +71,4 @@ class ThreadLocal { } }; -#endif // __TLS_H +#endif // __THREADLOCAL_H From 9840b2fb29b7c9e7738165051ef2d1e71fac990c Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 17 Jun 2026 17:41:32 +0000 Subject: [PATCH 03/18] An alternative to thread local --- ddprof-lib/src/main/cpp/livenessTracker.cpp | 26 ++++++++- ddprof-lib/src/main/cpp/threadLocal.h | 61 +++++++++++++-------- 2 files changed, 61 insertions(+), 26 deletions(-) diff --git a/ddprof-lib/src/main/cpp/livenessTracker.cpp b/ddprof-lib/src/main/cpp/livenessTracker.cpp index dbfc53e88..54c52353d 100644 --- a/ddprof-lib/src/main/cpp/livenessTracker.cpp +++ b/ddprof-lib/src/main/cpp/livenessTracker.cpp @@ -277,6 +277,24 @@ Error LivenessTracker::initialize(Arguments &args) { return _stored_error = Error::OK; } +static void* create_mt19937() { + return (void*)(new std::mt19937(std::random_device{}())); +} + +static void* create_uniform_real_distribution() { + return (void*)(new std::uniform_real_distribution<>(0, 1.0)); +} + +static void free_mt19937(void* p) { + std::mt19937* mt = (std::mt19937*)p; + delete mt; +} + +static void free_uniform_real_distribution(void* p) { + std::uniform_real_distribution<>* urd = (std::uniform_real_distribution<>*)p; + delete urd; +} + void LivenessTracker::track(JNIEnv *env, AllocEvent &event, jint tid, jobject object, u64 call_trace_id) { if (!_enabled) { @@ -288,11 +306,13 @@ void LivenessTracker::track(JNIEnv *env, AllocEvent &event, jint tid, return; } - static thread_local std::mt19937 gen(std::random_device{}()); - static thread_local std::uniform_real_distribution<> dis(0, 1.0); + static ThreadLocal gen; + static ThreadLocal*, create_uniform_real_distribution, free_uniform_real_distribution> dis; static ThreadLocal skipped; - if (_subsample_ratio < 1.0 && dis(gen) > _subsample_ratio) { + std::mt19937* genp = gen.get(); + std::uniform_real_distribution<>* disp = dis.get(); + if (_subsample_ratio < 1.0 && disp->operator()(*genp) > _subsample_ratio) { skipped.set(skipped.get() + static_cast(event._weight) * event._size); return; } diff --git a/ddprof-lib/src/main/cpp/threadLocal.h b/ddprof-lib/src/main/cpp/threadLocal.h index 05481731f..11487c8b2 100644 --- a/ddprof-lib/src/main/cpp/threadLocal.h +++ b/ddprof-lib/src/main/cpp/threadLocal.h @@ -3,52 +3,62 @@ * SPDX-License-Identifier: Apache-2.0 */ -#ifndef __THREADLOCAL_H -#define __THREADLOCAL_H +#ifndef __THREADLOCAL_H__ +#define __THREADLOCAL_H__ #include #include #include #include "arch.h" +// The function to create value if it does not exist +typedef void* (*CREATE_FUNC)(void); +// Cleanup the value when deleting the key typedef void (*CLEAN_FUNC)(void*); -template +template class ThreadLocal { protected: - static inline pthread_key_t _key; - static inline pthread_once_t _key_once = PTHREAD_ONCE_INIT; + pthread_key_t _key; - static void create_thread_key() { - pthread_key_create(&_key, F); - } public: ThreadLocal() { - pthread_once(&_key_once, create_thread_key);; + // Only support 64-bit platforms + static_assert(sizeof(void*) == 8); + int err; + while ((err = pthread_key_create(&_key, F)) == EAGAIN); + // What to do if we can not create a key? + assert(err == 0); } + ~ThreadLocal() { pthread_key_delete(_key); } - static void set(T value) { + void set(T value) { pthread_setspecific(_key, (const void*)value); } - static T get() { - return (T)pthread_getspecific(_key); + T get() { + void* p = pthread_getspecific(_key); + if (p == nullptr && C != nullptr) { + p = C(); + set((T)p); + } + return (T)p; } }; template <> class ThreadLocal { protected: - static inline pthread_key_t _key; - static inline pthread_once_t _key_once = PTHREAD_ONCE_INIT; - - static void create_thread_key() { - pthread_key_create(&_key, nullptr); - } + pthread_key_t _key; public: ThreadLocal() { - pthread_once(&_key_once, create_thread_key); + // Only support 64-bit platforms + static_assert(sizeof(void*) == 8); + int err; + while ((err = pthread_key_create(&_key, nullptr)) == EAGAIN); + // What to do if we can not create a key? + assert(err == 0); } ~ThreadLocal() { @@ -57,18 +67,23 @@ class ThreadLocal { // double <--> u64 cast, preserve bit format // Can use std::bit_cast after upgrade C++ version to 20 - static void set(double value) { + void set(double value) { u64 val; memcpy(&val, &value, sizeof(value)); pthread_setspecific(_key, (const void*)val); } - static double get() { - u64 val = (u64)pthread_getspecific(_key); + double get() { + void* p = pthread_getspecific(_key); + if (p == nullptr) { + return 0.0; + } + + u64 val = (u64)p; double value; memcpy(&value, &val, sizeof(val)); return value; } }; -#endif // __THREADLOCAL_H +#endif // __THREADLOCAL_H__ From a4aa1926e13a486dc9eeca6159b126961cf44a5a Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 17 Jun 2026 20:37:59 +0000 Subject: [PATCH 04/18] v2 --- ddprof-lib/src/main/cpp/livenessTracker.cpp | 18 +++++-- ddprof-lib/src/main/cpp/threadLocal.h | 54 ++++++++++++++++++--- 2 files changed, 60 insertions(+), 12 deletions(-) diff --git a/ddprof-lib/src/main/cpp/livenessTracker.cpp b/ddprof-lib/src/main/cpp/livenessTracker.cpp index 54c52353d..03f6b2a15 100644 --- a/ddprof-lib/src/main/cpp/livenessTracker.cpp +++ b/ddprof-lib/src/main/cpp/livenessTracker.cpp @@ -278,10 +278,16 @@ Error LivenessTracker::initialize(Arguments &args) { } static void* create_mt19937() { + // std::mt19937 construction does not throw exception. + // However, memory allocation can fail. When it happens, application is likely + // to crash anyway. return (void*)(new std::mt19937(std::random_device{}())); } static void* create_uniform_real_distribution() { + // std::uniform_real_distribution construction does not throw exception. + // However, memory allocation can fail. When it happens, application is likely + // to crash anyway. return (void*)(new std::uniform_real_distribution<>(0, 1.0)); } @@ -310,11 +316,13 @@ void LivenessTracker::track(JNIEnv *env, AllocEvent &event, jint tid, static ThreadLocal*, create_uniform_real_distribution, free_uniform_real_distribution> dis; static ThreadLocal skipped; - std::mt19937* genp = gen.get(); - std::uniform_real_distribution<>* disp = dis.get(); - if (_subsample_ratio < 1.0 && disp->operator()(*genp) > _subsample_ratio) { - skipped.set(skipped.get() + static_cast(event._weight) * event._size); - return; + if (_subsample_ratio < 1.0) { + std::mt19937* genp = gen.get(); + std::uniform_real_distribution<>* disp = dis.get(); + if (disp->operator()(*genp) > _subsample_ratio) { + skipped.set(skipped.get() + static_cast(event._weight) * event._size); + return; + } } jweak ref = env->NewWeakGlobalRef(object); diff --git a/ddprof-lib/src/main/cpp/threadLocal.h b/ddprof-lib/src/main/cpp/threadLocal.h index 11487c8b2..b7795f3d2 100644 --- a/ddprof-lib/src/main/cpp/threadLocal.h +++ b/ddprof-lib/src/main/cpp/threadLocal.h @@ -11,6 +11,35 @@ #include #include "arch.h" +/** + * This file implements an alternative to C/C++ thread local. + * Due to some restrictions of the language implementations, especially, on musl/aarch64, + * they cannot be safely used in profiler. + * + * How to use? + * A ThreadLocal should be declared as a static variable, e.g. + * + * static void* create_my_object() { + * return new MyObject(); + * } + * + * static void delete_my_object(void* p) { + * MyObject* obj = (MyObject*)p; + * delete obj; + * } + * + * static ThreadLocal var; + * MyObject* value = var.get(); + * ... + * var.clear(); + * ... + * MyObject* new_value = new MyObject(); + * var.set(new_value); + * ... + * var.clear(); + * + */ + // The function to create value if it does not exist typedef void* (*CREATE_FUNC)(void); // Cleanup the value when deleting the key @@ -22,10 +51,7 @@ class ThreadLocal { public: ThreadLocal() { - // Only support 64-bit platforms - static_assert(sizeof(void*) == 8); - int err; - while ((err = pthread_key_create(&_key, F)) == EAGAIN); + int err = pthread_key_create(&_key, F); // What to do if we can not create a key? assert(err == 0); } @@ -33,6 +59,7 @@ class ThreadLocal { ~ThreadLocal() { pthread_key_delete(_key); } + void set(T value) { pthread_setspecific(_key, (const void*)value); } @@ -45,6 +72,15 @@ class ThreadLocal { } return (T)p; } + + // Clear the value + void clear() { + void* p = nullptr; + if (F != nullptr && (p = pthread_getspecific(_key)) != nullptr) { + pthread_setspecific(_key, nullptr); + F(p); + } + } }; template <> @@ -53,10 +89,10 @@ class ThreadLocal { pthread_key_t _key; public: ThreadLocal() { - // Only support 64-bit platforms + // Only support 64-bit platforms, double and void* are the same size static_assert(sizeof(void*) == 8); - int err; - while ((err = pthread_key_create(&_key, nullptr)) == EAGAIN); + static_assert(sizeof(double) == 8); + int err = pthread_key_create(&_key, nullptr); // What to do if we can not create a key? assert(err == 0); } @@ -84,6 +120,10 @@ class ThreadLocal { memcpy(&value, &val, sizeof(val)); return value; } + + void clear() { + pthread_setspecific(_key, nullptr); + } }; #endif // __THREADLOCAL_H__ From 17889cec6610cc067ccaedb7ba1090f5cd1676fc Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 18 Jun 2026 12:47:18 +0000 Subject: [PATCH 05/18] Added a test --- ddprof-lib/src/test/cpp/threadLocal_ut.cpp | 241 +++++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 ddprof-lib/src/test/cpp/threadLocal_ut.cpp diff --git a/ddprof-lib/src/test/cpp/threadLocal_ut.cpp b/ddprof-lib/src/test/cpp/threadLocal_ut.cpp new file mode 100644 index 000000000..4ab494d40 --- /dev/null +++ b/ddprof-lib/src/test/cpp/threadLocal_ut.cpp @@ -0,0 +1,241 @@ +/* + * Copyright 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 +#include "threadLocal.h" +#include "gtest_crash_handler.h" +#include +#include +#include +#include +#include + +static constexpr char THREADLOCAL_TEST_NAME[] = "ThreadLocalTest"; + +// NOTE on the instances below being namespace-scope `static`: +// ThreadLocal's destructor calls pthread_key_delete() but never resets +// _key_once, so destroying an instance and then constructing another of the +// same type would operate on a deleted key (UB). The production code only ever +// uses `static ThreadLocal<...>` locals (destroyed at process exit), so the +// tests mirror that: one long-lived instance per distinct type. Per-thread +// behaviour is exercised in freshly spawned threads, which start with empty +// thread-specific storage. + +// ---- generic pointer specialization: plain set/get, no create/clean ---- +static ThreadLocal g_int_tl; + +// ---- lazy-create + cleanup instrumentation ---- +static std::atomic g_created{0}; +static std::atomic g_freed{0}; + +static void *create_tracked() { + g_created.fetch_add(1, std::memory_order_relaxed); + return new int(1234); +} + +static void free_tracked(void *p) { + g_freed.fetch_add(1, std::memory_order_relaxed); + delete static_cast(p); +} + +static ThreadLocal g_tracked_tl; + +// ---- double specialization ---- +static ThreadLocal g_double_tl; + +class ThreadLocalTest : public ::testing::Test { +protected: + void SetUp() override { + installGtestCrashHandler(); + } + void TearDown() override { + restoreDefaultSignalHandlers(); + } +}; + +// set() then get() round-trips a value on the same thread. +TEST_F(ThreadLocalTest, Generic_SetGetRoundTrip) { + g_int_tl.set(42); + EXPECT_EQ(42, g_int_tl.get()); + g_int_tl.set(-7); + EXPECT_EQ(-7, g_int_tl.get()); +} + +// Each thread sees only its own value: storage is per-thread, not shared. +TEST_F(ThreadLocalTest, Generic_PerThreadIsolation) { + constexpr int kThreads = 8; + std::atomic ready{0}; + std::atomic go{false}; + std::vector threads; + std::atomic mismatches{0}; + + for (int i = 0; i < kThreads; ++i) { + threads.emplace_back([&, i] { + // Fresh thread: storage must start empty. + if (g_int_tl.get() != 0) { + mismatches.fetch_add(1, std::memory_order_relaxed); + } + g_int_tl.set(i + 1); + + // Barrier: every thread writes before any thread reads back, so a + // shared (buggy) slot would be observably clobbered. + ready.fetch_add(1, std::memory_order_relaxed); + while (!go.load(std::memory_order_acquire)) { + } + + if (g_int_tl.get() != static_cast(i + 1)) { + mismatches.fetch_add(1, std::memory_order_relaxed); + } + }); + } + + while (ready.load(std::memory_order_relaxed) != kThreads) { + } + go.store(true, std::memory_order_release); + + for (auto &t : threads) { + t.join(); + } + EXPECT_EQ(0, mismatches.load()); +} + +// A fresh thread that never called set() reads the zero-initialized default. +TEST_F(ThreadLocalTest, Generic_UnsetIsZero) { + intptr_t observed = -1; + std::thread t([&] { observed = g_int_tl.get(); }); + t.join(); + EXPECT_EQ(0, observed); +} + +// The create function lazily initializes storage on first get() and is invoked +// exactly once per thread; subsequent get()s return the same pointer. +TEST_F(ThreadLocalTest, Lazy_CreateOncePerThread) { + g_created.store(0, std::memory_order_relaxed); + g_freed.store(0, std::memory_order_relaxed); + + int *first = nullptr; + int *second = nullptr; + int value = 0; + std::thread t([&] { + first = g_tracked_tl.get(); + second = g_tracked_tl.get(); + // Read the payload here: free_tracked() deletes it on thread exit, so + // dereferencing first/second after join() would be use-after-free. + value = *first; + }); + t.join(); + + ASSERT_NE(nullptr, first); + EXPECT_EQ(first, second); // same instance reused (pointer compare only) + EXPECT_EQ(1234, value); // created via create_tracked() + EXPECT_EQ(1, g_created.load()); // created exactly once +} + +// The clean function runs when the owning thread exits, freeing per-thread state. +TEST_F(ThreadLocalTest, Lazy_CleanupOnThreadExit) { + g_created.store(0, std::memory_order_relaxed); + g_freed.store(0, std::memory_order_relaxed); + + std::thread t([&] { + // Touch storage so a value exists to be cleaned up on exit. + ASSERT_NE(nullptr, g_tracked_tl.get()); + }); + t.join(); + // After join the thread has fully terminated, so its TSD destructor + // (free_tracked) must have run. + EXPECT_EQ(1, g_created.load()); + EXPECT_EQ(1, g_freed.load()); +} + +// Independent threads each create and free their own value. +TEST_F(ThreadLocalTest, Lazy_CleanupAcrossManyThreads) { + g_created.store(0, std::memory_order_relaxed); + g_freed.store(0, std::memory_order_relaxed); + + constexpr int kThreads = 16; + std::vector threads; + for (int i = 0; i < kThreads; ++i) { + threads.emplace_back([] { (void)g_tracked_tl.get(); }); + } + for (auto &t : threads) { + t.join(); + } + EXPECT_EQ(kThreads, g_created.load()); + EXPECT_EQ(kThreads, g_freed.load()); +} + +// The double specialization preserves the exact bit pattern through the +// u64<->void* round-trip (on 64-bit targets, where a double fits in a pointer). +TEST_F(ThreadLocalTest, Double_RoundTripPreservesValue) { + static_assert(sizeof(void *) >= sizeof(double), + "ThreadLocal requires pointer >= double width"); + + const double values[] = { + 0.0, + 1.0, + -1.0, + 3.141592653589793, + -2.718281828459045, + 1.7976931348623157e308, // near DBL_MAX + 2.2250738585072014e-308, // near DBL_MIN (smallest normal) + 4.9e-324, // smallest subnormal + }; + + std::atomic mismatches{0}; + std::thread t([&] { + for (double v : values) { + g_double_tl.set(v); + if (g_double_tl.get() != v) { + mismatches.fetch_add(1, std::memory_order_relaxed); + } + } + }); + t.join(); + EXPECT_EQ(0, mismatches.load()); +} + +// An unset double reads back as 0.0 (matches the original `thread_local double = 0`). +TEST_F(ThreadLocalTest, Double_UnsetIsZero) { + double observed = -1.0; + std::thread t([&] { observed = g_double_tl.get(); }); + t.join(); + EXPECT_EQ(0.0, observed); +} + +// Per-thread accumulation mirrors LivenessTracker's `skipped` usage: each thread +// keeps its own running sum, isolated from the others. +TEST_F(ThreadLocalTest, Double_PerThreadAccumulation) { + constexpr int kThreads = 8; + constexpr int kIters = 1000; + std::atomic mismatches{0}; + std::vector threads; + + for (int i = 0; i < kThreads; ++i) { + threads.emplace_back([&, i] { + const double step = static_cast(i + 1); + for (int k = 0; k < kIters; ++k) { + g_double_tl.set(g_double_tl.get() + step); + } + if (g_double_tl.get() != step * kIters) { + mismatches.fetch_add(1, std::memory_order_relaxed); + } + }); + } + for (auto &t : threads) { + t.join(); + } + EXPECT_EQ(0, mismatches.load()); +} From f38c9cdb79c3a54e2065629882edee77145ce35c Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 18 Jun 2026 09:57:50 -0400 Subject: [PATCH 06/18] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/livenessTracker.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ddprof-lib/src/main/cpp/livenessTracker.cpp b/ddprof-lib/src/main/cpp/livenessTracker.cpp index 03f6b2a15..d7aeb0f4c 100644 --- a/ddprof-lib/src/main/cpp/livenessTracker.cpp +++ b/ddprof-lib/src/main/cpp/livenessTracker.cpp @@ -285,9 +285,8 @@ static void* create_mt19937() { } static void* create_uniform_real_distribution() { - // std::uniform_real_distribution construction does not throw exception. - // However, memory allocation can fail. When it happens, application is likely - // to crash anyway. + // std::uniform_real_distribution<> construction is noexcept, but `new` may throw. + // If allocation fails the process is likely to abort anyway. return (void*)(new std::uniform_real_distribution<>(0, 1.0)); } From b387308f079f6be11fadf43c184a528116266b28 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 18 Jun 2026 09:58:12 -0400 Subject: [PATCH 07/18] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/livenessTracker.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ddprof-lib/src/main/cpp/livenessTracker.cpp b/ddprof-lib/src/main/cpp/livenessTracker.cpp index d7aeb0f4c..3881879d3 100644 --- a/ddprof-lib/src/main/cpp/livenessTracker.cpp +++ b/ddprof-lib/src/main/cpp/livenessTracker.cpp @@ -278,9 +278,9 @@ Error LivenessTracker::initialize(Arguments &args) { } static void* create_mt19937() { - // std::mt19937 construction does not throw exception. - // However, memory allocation can fail. When it happens, application is likely - // to crash anyway. + // std::mt19937 itself is noexcept, but std::random_device and `new` may throw. + // If that happens we let the failure terminate the process (same outcome as + // failing thread_local initialization previously). return (void*)(new std::mt19937(std::random_device{}())); } From c2b7014ebe2a24b73ac02836317e272d2c48accd Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 18 Jun 2026 09:58:30 -0400 Subject: [PATCH 08/18] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/threadLocal.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ddprof-lib/src/main/cpp/threadLocal.h b/ddprof-lib/src/main/cpp/threadLocal.h index b7795f3d2..79a00e10c 100644 --- a/ddprof-lib/src/main/cpp/threadLocal.h +++ b/ddprof-lib/src/main/cpp/threadLocal.h @@ -61,7 +61,8 @@ class ThreadLocal { } void set(T value) { - pthread_setspecific(_key, (const void*)value); + int err = pthread_setspecific(_key, (const void*)value); + assert(err == 0); } T get() { From a632c1c85914d22ac1ebf74116a116de545782a9 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 18 Jun 2026 09:59:14 -0400 Subject: [PATCH 09/18] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/threadLocal.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ddprof-lib/src/main/cpp/threadLocal.h b/ddprof-lib/src/main/cpp/threadLocal.h index 79a00e10c..53fa3e44b 100644 --- a/ddprof-lib/src/main/cpp/threadLocal.h +++ b/ddprof-lib/src/main/cpp/threadLocal.h @@ -78,7 +78,8 @@ class ThreadLocal { void clear() { void* p = nullptr; if (F != nullptr && (p = pthread_getspecific(_key)) != nullptr) { - pthread_setspecific(_key, nullptr); + int err = pthread_setspecific(_key, nullptr); + assert(err == 0); F(p); } } From a3d895061ffdfa267e89e068f70a9253a370bcd0 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 18 Jun 2026 09:59:29 -0400 Subject: [PATCH 10/18] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/threadLocal.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ddprof-lib/src/main/cpp/threadLocal.h b/ddprof-lib/src/main/cpp/threadLocal.h index 53fa3e44b..e2d8394df 100644 --- a/ddprof-lib/src/main/cpp/threadLocal.h +++ b/ddprof-lib/src/main/cpp/threadLocal.h @@ -108,7 +108,8 @@ class ThreadLocal { void set(double value) { u64 val; memcpy(&val, &value, sizeof(value)); - pthread_setspecific(_key, (const void*)val); + int err = pthread_setspecific(_key, (const void*)val); + assert(err == 0); } double get() { From 792a4a3967f7df9d17ff317a6aafdf59473225fc Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 18 Jun 2026 09:59:48 -0400 Subject: [PATCH 11/18] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/threadLocal.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ddprof-lib/src/main/cpp/threadLocal.h b/ddprof-lib/src/main/cpp/threadLocal.h index e2d8394df..e4b1a89c6 100644 --- a/ddprof-lib/src/main/cpp/threadLocal.h +++ b/ddprof-lib/src/main/cpp/threadLocal.h @@ -125,7 +125,8 @@ class ThreadLocal { } void clear() { - pthread_setspecific(_key, nullptr); + int err = pthread_setspecific(_key, nullptr); + assert(err == 0); } }; From f9c9b28cd932f914114c0d69eaf79da7e22e827e Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 18 Jun 2026 10:00:21 -0400 Subject: [PATCH 12/18] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/threadLocal.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ddprof-lib/src/main/cpp/threadLocal.h b/ddprof-lib/src/main/cpp/threadLocal.h index e4b1a89c6..cc507ccb7 100644 --- a/ddprof-lib/src/main/cpp/threadLocal.h +++ b/ddprof-lib/src/main/cpp/threadLocal.h @@ -90,6 +90,9 @@ class ThreadLocal { protected: pthread_key_t _key; public: + ThreadLocal(const ThreadLocal&) = delete; + ThreadLocal& operator=(const ThreadLocal&) = delete; + ThreadLocal() { // Only support 64-bit platforms, double and void* are the same size static_assert(sizeof(void*) == 8); From 86c2e2ef07a92db7647358276861adfea45f1fb6 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 18 Jun 2026 10:00:39 -0400 Subject: [PATCH 13/18] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/threadLocal.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ddprof-lib/src/main/cpp/threadLocal.h b/ddprof-lib/src/main/cpp/threadLocal.h index cc507ccb7..fa1debc77 100644 --- a/ddprof-lib/src/main/cpp/threadLocal.h +++ b/ddprof-lib/src/main/cpp/threadLocal.h @@ -3,8 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -#ifndef __THREADLOCAL_H__ -#define __THREADLOCAL_H__ +#ifndef _THREADLOCAL_H +#define _THREADLOCAL_H #include #include From 2e788fdbdd6818f6364379c20489e354da40bca5 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 18 Jun 2026 10:00:56 -0400 Subject: [PATCH 14/18] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/threadLocal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddprof-lib/src/main/cpp/threadLocal.h b/ddprof-lib/src/main/cpp/threadLocal.h index fa1debc77..149e0759c 100644 --- a/ddprof-lib/src/main/cpp/threadLocal.h +++ b/ddprof-lib/src/main/cpp/threadLocal.h @@ -133,4 +133,4 @@ class ThreadLocal { } }; -#endif // __THREADLOCAL_H__ +#endif // _THREADLOCAL_H From 282a346e327ca5dbe033eb71ffca0966c52f9282 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 18 Jun 2026 10:01:10 -0400 Subject: [PATCH 15/18] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/threadLocal.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ddprof-lib/src/main/cpp/threadLocal.h b/ddprof-lib/src/main/cpp/threadLocal.h index 149e0759c..7cf9816b5 100644 --- a/ddprof-lib/src/main/cpp/threadLocal.h +++ b/ddprof-lib/src/main/cpp/threadLocal.h @@ -50,6 +50,9 @@ class ThreadLocal { pthread_key_t _key; public: + ThreadLocal(const ThreadLocal&) = delete; + ThreadLocal& operator=(const ThreadLocal&) = delete; + ThreadLocal() { int err = pthread_key_create(&_key, F); // What to do if we can not create a key? From 5cf4a1c419fe439464c87af5e3bff308a115e463 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 18 Jun 2026 10:02:16 -0400 Subject: [PATCH 16/18] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/test/cpp/threadLocal_ut.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/ddprof-lib/src/test/cpp/threadLocal_ut.cpp b/ddprof-lib/src/test/cpp/threadLocal_ut.cpp index 4ab494d40..453b5d1e9 100644 --- a/ddprof-lib/src/test/cpp/threadLocal_ut.cpp +++ b/ddprof-lib/src/test/cpp/threadLocal_ut.cpp @@ -18,7 +18,6 @@ #include "threadLocal.h" #include "gtest_crash_handler.h" #include -#include #include #include #include From 337b80fb5bb2810d996c55a31b2ac38d7af38e60 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 18 Jun 2026 10:02:57 -0400 Subject: [PATCH 17/18] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/test/cpp/threadLocal_ut.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/ddprof-lib/src/test/cpp/threadLocal_ut.cpp b/ddprof-lib/src/test/cpp/threadLocal_ut.cpp index 453b5d1e9..89959bf71 100644 --- a/ddprof-lib/src/test/cpp/threadLocal_ut.cpp +++ b/ddprof-lib/src/test/cpp/threadLocal_ut.cpp @@ -25,13 +25,10 @@ static constexpr char THREADLOCAL_TEST_NAME[] = "ThreadLocalTest"; // NOTE on the instances below being namespace-scope `static`: -// ThreadLocal's destructor calls pthread_key_delete() but never resets -// _key_once, so destroying an instance and then constructing another of the -// same type would operate on a deleted key (UB). The production code only ever -// uses `static ThreadLocal<...>` locals (destroyed at process exit), so the -// tests mirror that: one long-lived instance per distinct type. Per-thread -// behaviour is exercised in freshly spawned threads, which start with empty -// thread-specific storage. +// Keep the ThreadLocal instances alive for the duration of the test binary so +// their pthread keys are not repeatedly created/deleted across tests. +// This mirrors production usage where ThreadLocal instances are typically static +// and live until process exit. // ---- generic pointer specialization: plain set/get, no create/clean ---- static ThreadLocal g_int_tl; From c19028648980fe02b8c677f92af0f866d19df826 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 18 Jun 2026 14:24:26 +0000 Subject: [PATCH 18/18] fix --- ddprof-lib/src/main/cpp/threadLocal.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ddprof-lib/src/main/cpp/threadLocal.h b/ddprof-lib/src/main/cpp/threadLocal.h index 7cf9816b5..b535016cc 100644 --- a/ddprof-lib/src/main/cpp/threadLocal.h +++ b/ddprof-lib/src/main/cpp/threadLocal.h @@ -82,8 +82,11 @@ class ThreadLocal { void* p = nullptr; if (F != nullptr && (p = pthread_getspecific(_key)) != nullptr) { int err = pthread_setspecific(_key, nullptr); - assert(err == 0); - F(p); + // Safety: if reset the value failed, get() can see staled value if + // it is freed. + if (err == 0) { + F(p); + } } } };