Skip to content

Commit 3c8190d

Browse files
jbachorikclaude
andcommitted
refactor: address review findings on thread-context factory split
Restore ProfiledThread factory registration in gtest binaries, null-guard nullable currentProfiled() in javaApi.cpp, re-read profiler-presence lazily instead of latching it, fix support/profiler LOADED-state tracking in LibraryLoader, resolve Dictionary encoding before the detach window, and fail closed consistently on Dictionary/attrs_data overflow. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 7d10bc3 commit 3c8190d

21 files changed

Lines changed: 1328 additions & 46 deletions

.github/workflows/test_workflow.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ jobs:
148148
exit 1
149149
fi
150150
- name: Verify libJavaSupport ABI
151+
# NOTE: this ABI gate only runs on the glibc/musl Linux matrix legs
152+
# (nm -D against libJavaSupport.so). There is no equivalent check for
153+
# macOS builds — the otel_thread_ctx_v1 export guarantee documented
154+
# below is verified on Linux only.
151155
if: success()
152156
run: |
153157
SUPPORT_LIB=$(find ddprof-lib/build/lib -name "libJavaSupport.so" | head -1)
@@ -158,7 +162,15 @@ jobs:
158162
echo "$PROFILER_UNDEFINED"
159163
exit 1
160164
fi
161-
echo "ABI gate passed — no profiler symbols in libJavaSupport.so"
165+
# ContextExtractionToSupportPlan Phase E.4: otel_thread_ctx_v1 must be exported
166+
# from libJavaSupport.so so external profilers can discover the OTEP thread
167+
# context record with only the support library loaded (no profiler).
168+
if ! nm -D "$SUPPORT_LIB" | grep -q 'otel_thread_ctx_v1'; then
169+
echo "ERROR: libJavaSupport.so does not export otel_thread_ctx_v1"
170+
nm -D "$SUPPORT_LIB" | grep -i otel || true
171+
exit 1
172+
fi
173+
echo "ABI gate passed — no profiler symbols in libJavaSupport.so, otel_thread_ctx_v1 exported"
162174
- name: Generate Unwinding Report
163175
if: success() && matrix.config == 'debug'
164176
run: |

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,9 @@ Java_com_datadoghq_profiler_JavaProfiler_getSamples(JNIEnv *env,
137137
extern "C" DLLEXPORT void JNICALL
138138
JavaCritical_com_datadoghq_profiler_JavaProfiler_filterThreadAdd0() {
139139
ProfiledThread *current = ProfiledThread::currentProfiled();
140-
assert(current != nullptr);
140+
if (current == nullptr) {
141+
return;
142+
}
141143
int tid = current->tid();
142144
if (unlikely(tid < 0)) {
143145
return;
@@ -167,7 +169,9 @@ JavaCritical_com_datadoghq_profiler_JavaProfiler_filterThreadAdd0() {
167169
extern "C" DLLEXPORT void JNICALL
168170
JavaCritical_com_datadoghq_profiler_JavaProfiler_filterThreadRemove0() {
169171
ProfiledThread *current = ProfiledThread::currentProfiled();
170-
assert(current != nullptr);
172+
if (current == nullptr) {
173+
return;
174+
}
171175
int tid = current->tid();
172176
if (unlikely(tid < 0)) {
173177
return;

ddprof-lib/src/main/cpp/support/otel_context.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ DLLEXPORT extern thread_local OtelThreadContextRecord* otel_thread_ctx_v1;
7575
*
7676
* Signal safety: signal handlers must never access
7777
* otel_thread_ctx_v1 directly (TLS lazy init can deadlock
78-
* in musl). Instead they read via ProfiledThread::getOtelContextRecord().
78+
* in musl). Instead they read via ThreadContext::getOtelContextRecord().
7979
*/
8080

8181
#endif /* _OTEL_CONTEXT_H */
File renamed without changes.
File renamed without changes.

ddprof-lib/src/main/cpp/support/threadContext.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ bool ThreadContext::_tls_key_initialized = false;
1313
static ThreadContext* defaultFactory(int tid) { return new ThreadContext(tid); }
1414
std::atomic<ThreadContextFactory> g_thread_context_factory{defaultFactory};
1515

16+
void resetThreadContextFactory() {
17+
g_thread_context_factory.store(defaultFactory, std::memory_order_release);
18+
}
19+
1620
void ThreadContext::initTLSKey() {
1721
static pthread_once_t tls_initialized = PTHREAD_ONCE_INIT;
1822
pthread_once(&tls_initialized, doInitTLSKey);

ddprof-lib/src/main/cpp/support/threadContext.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,20 @@ class ThreadContext : public ThreadLocalData {
7676
}
7777
// Deletes a ThreadContext returned by clearCurrentThreadTLS().
7878
static void deleteForTest(ThreadContext *tls) { delete tls; }
79+
80+
// Writes span_id/root_span_id directly on the base object and marks the
81+
// record valid, without requiring a ProfiledThread or the JNI put() path.
82+
// Exercises the base-object write path for the defensive-mode test
83+
// (ContextExtractionToSupportPlan Phase E.5): writing context via a plain
84+
// ThreadContext before any profiler factory is registered.
85+
inline void setContextForTest(u64 span_id, u64 root_span_id) {
86+
OtelThreadContextRecord *record = getOtelContextRecord();
87+
for (int i = 0; i < 8; i++) {
88+
record->span_id[i] = (uint8_t)(span_id >> (8 * (7 - i)));
89+
}
90+
_otel_local_root_span_id = root_span_id;
91+
__atomic_store_n(&record->valid, (uint8_t)1, __ATOMIC_RELEASE);
92+
}
7993
#endif
8094

8195
inline int tid() { return _tid; }
@@ -113,4 +127,9 @@ class ThreadContext : public ThreadLocalData {
113127
typedef ThreadContext* (*ThreadContextFactory)(int tid);
114128
extern std::atomic<ThreadContextFactory> g_thread_context_factory;
115129

130+
// Restores g_thread_context_factory to the support-only default (plain
131+
// ThreadContext), undoing a profiler-installed factory. Called at profiler
132+
// teardown so a subsequent re-init starts from a clean slate.
133+
void resetThreadContextFactory();
134+
116135
#endif // _SUPPORT_THREAD_CONTEXT_H

ddprof-lib/src/main/cpp/support/vmstructs-abi.symbols

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,21 @@ Java_com_datadoghq_profiler_JVMAccess_findIntJVMFlag0
2424
Java_com_datadoghq_profiler_JVMAccess_findFloatJVMFlag0
2525
Java_com_datadoghq_profiler_JVMAccess_healthCheck0
2626

27+
# OTEP #4947 TLS discovery pointer (support/otel_context.cpp) — external profilers
28+
# locate this via ELF dynsym; unmangled global-scope C++ variable (no extern "C" needed).
29+
# Trailing '*' (fnmatch-style, supported by both the GNU ld version script and the
30+
# macOS export-list generator) matches only this one symbol on Linux. On Mach-O the
31+
# raw TLS variable is always kept local by the ABI (only the compiler-generated
32+
# _ZTW... "TLV wrapper" accessor is ever external) — an exact-name entry would make
33+
# ld64 hard-fail with "symbol(s) not found", so the wildcard form degrades to a
34+
# harmless no-op match there instead. macOS is dev-degraded (§2.1); Linux is ship.
35+
otel_thread_ctx_v1*
36+
37+
# Context JNI entry points (support/contextApi.cpp; support-only, no profiler dependency)
38+
Java_com_datadoghq_profiler_ContextStorage_initializeContextTLS0
39+
Java_com_datadoghq_profiler_OTelContext_setProcessCtx0
40+
Java_com_datadoghq_profiler_OTelContext_readProcessCtx0
41+
2742
# Crash-protection probe (C linkage + helpers)
2843
g_crash_protection_probe
2944
_Z25crashProtectionProbeResetv

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

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,6 @@
1111
#include <cstring>
1212
#include <time.h>
1313

14-
// The shared ThreadContext TLS slot defaults to producing plain ThreadContext
15-
// instances (support-only build). This translation unit is only linked into
16-
// the full profiler build, so it commits the factory to produce ProfiledThread
17-
// instead, at library-load time — before any thread (including the one that
18-
// calls Profiler::start()) can observe the TLS slot and lazily allocate a
19-
// plain ThreadContext.
20-
static ThreadContext *newProfiledThread(int tid) { return ProfiledThread::forTid(tid); }
21-
22-
namespace {
23-
struct ProfiledThreadFactoryInstaller {
24-
ProfiledThreadFactoryInstaller() {
25-
g_thread_context_factory.store(newProfiledThread, std::memory_order_release);
26-
}
27-
};
28-
static ProfiledThreadFactoryInstaller profiled_thread_factory_installer;
29-
} // namespace
30-
3114
void ProfiledThread::initCurrentThread() {
3215
// JVMTI callback path - does NOT use buffer
3316
// Allocate dedicated ProfiledThread for Java threads (not from buffer)

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,12 @@ bool VM::initProfilerBridge(JavaVM *vm, bool attach) {
462462
},
463463
std::memory_order_release);
464464

465+
// Profiler is present: per-thread OTEL context storage must be backed by
466+
// the full ProfiledThread record, not the support-only ThreadContext base.
467+
g_thread_context_factory.store(
468+
[](int tid) -> ThreadContext* { return ProfiledThread::forTid(tid); },
469+
std::memory_order_release);
470+
465471
CodeCache *lib = openJvmLibrary();
466472
if (lib == nullptr) {
467473
return false;
@@ -754,4 +760,5 @@ extern "C" DLLEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
754760
crashProtectionProbeReset();
755761
VMThread::resetIsJavaThreadProbe();
756762
resetIsInSignalProbe();
763+
resetThreadContextFactory();
757764
}

0 commit comments

Comments
 (0)