-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy paththreadLocalData.cpp
More file actions
178 lines (159 loc) · 5.32 KB
/
Copy paththreadLocalData.cpp
File metadata and controls
178 lines (159 loc) · 5.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
/*
* Copyright 2026, Datadog, Inc.
* SPDX-License-Identifier: Apache-2.0
*/
#include "faultInjection.h"
#include "threadLocalData.inline.h"
#include "threadLocalDataPool.h"
#include "context_api.h"
#include "guards.h"
#include "otel_context.h"
#include "os.h"
#include <cassert>
#include <cstring>
#include <time.h>
// Namespace-scope static: the ctor (pthread_key_create) runs during library
// load — single-threaded, before profiling signals or the pthread_create
// interceptor are armed. So current()/isThreadKeyValid() may read _key without
// synchronization: the ctor's write happens-before any later thread/signal that
// reads it.
ThreadLocal<ProfiledThread*, nullptr, ProfiledThread::freeValue> ProfiledThread::_current_thread;
#ifdef UNIT_TEST
void (*ProfiledThread::forTidTestHook)() = nullptr;
#endif
bool ProfiledThread::supportPriming() {
// Key must be valid
assert(_current_thread.isKeyValid());
if (OS::isMusl()) {
return true;
}
#ifdef __GLIBC__
bool rc = _current_thread.key() < PTHREAD_KEY_2NDLEVEL_SIZE;
return INJECT_FAULT_BOOL_HIGH(rc);
#else
// Neither musl nor glibc (e.g. macOS libpthread): PTHREAD_KEY_2NDLEVEL_SIZE
// is a glibc NPTL implementation detail (see threadLocalData.h) that doesn't
// describe this libc's pthread_key_t allocation scheme. Fail safe by
// disabling signal-handler TLS priming rather than assuming glibc-compatible
// pthread_setspecific behavior.
return false;
#endif
}
ProfiledThread* ProfiledThread::initCurrentThread() {
if (!isThreadKeyValid()) {
return nullptr;
}
ProfiledThread* tls = _current_thread.get();
if (tls == nullptr) {
int tid = OS::threadId();
tls = ProfiledThread::forTid(tid);
// forTid()'s `new` can recurse into a malloc hook (e.g. nativemem
// profiling) that calls acquireCurrent() before we get here. Seeing TLS
// still unset, it claims a ThreadLocalDataPool slot and publishes it to
// TLS. Release that slot instead of silently clobbering it below, or it
// stays claimed forever and the pool leaks one slot per occurrence.
//
// Not a signal race: initCurrentThread() is documented above as callable
// only with profiling signals already blocked on this thread (the
// SignalBlocker in initCurrentThreadSignalSafe(), or an explicit one at
// the call site otherwise -- see e.g. libraryPatcher_linux.cpp's
// init_tls_and_register()). So no signal handler can run -- and
// therefore none can observe or act on the primed slot -- between the
// claim above and the release below.
ProfiledThread* primed = _current_thread.get();
if (primed != nullptr) {
ThreadLocalDataPool::release(primed);
}
_current_thread.set(tls);
}
return tls;
}
ProfiledThread* ProfiledThread::initCurrentThreadSignalSafe() {
if (!isThreadKeyValid()) {
return nullptr;
}
ProfiledThread* cur = current();
if (cur == nullptr) {
SignalBlocker blocker;
return initCurrentThread();
} else {
return cur;
}
}
void ProfiledThread::freeValue(void* value) {
// Block future profiling signals that prevents re-allocating
// ProfiledThread that cannot be released - memory leak
blockProfilingForExit();
ProfiledThread* pt = reinterpret_cast<ProfiledThread*>(value);
if (!ThreadLocalDataPool::release(pt)) {
// Sole deletion site for a ProfiledThread (invoked by the ThreadLocal
// destructor callback), so the THREAD_LOCAL decrement belongs here. Record
// after the delete, consistent with the other decrement sites.
delete pt;
NativeMem::record(NM_THREAD_LOCAL, -(long long)sizeof(ProfiledThread));
}
}
void ProfiledThread::release() {
_current_thread.clear();
}
#ifdef UNIT_TEST
ProfiledThread* ProfiledThread::clearCurrentThreadTLS() {
assert(isThreadKeyValid() && "Should not reach here - profiling should have been disabled");
ProfiledThread* pt = _current_thread.get();
_current_thread.set(nullptr);
return pt;
}
void ProfiledThread::deleteForTest(ProfiledThread* pt) {
if (!ThreadLocalDataPool::release(pt)) {
delete pt;
NativeMem::record(NM_THREAD_LOCAL, -(long long)sizeof(ProfiledThread));
}
}
#endif
int ProfiledThread::currentTid() {
ProfiledThread *tls = current();
if (tls != NULL) {
return tls->tid();
}
return OS::threadId();
}
Context ProfiledThread::snapshotContext(size_t numAttrs) {
Context ctx = {};
u64 span_id = 0, root_span_id = 0;
if (ContextApi::get(span_id, root_span_id)) {
ctx.spanId = span_id;
ctx.rootSpanId = root_span_id;
size_t count = numAttrs < DD_TAGS_CAPACITY ? numAttrs : DD_TAGS_CAPACITY;
for (size_t i = 0; i < count; i++) {
ctx.setTag(i, _otel_tag_encodings[i]);
}
}
return ctx;
}
void ProfiledThread::unclaimAndReset() {
_unwinding_Java = false;
_jmp_buf = nullptr;
_pc = 0;
_sp = 0;
_span_id = 0;
_crash_depth = 0;
_tid = 0;
_cpu_epoch = 0;
_wall_epoch = 0;
_call_trace_id = 0;
_recording_epoch = 0;
_park_block_token = 0;
_filter_slot_id = -1;
_init_window = 0;
_signal_depth = 0;
_in_critical_section = false;
_otel_ctx_initialized = false;
_otel_ctx_record = {};
_otel_local_root_span_id = 0;
for (int index = 0; index < DD_TAGS_CAPACITY; index++) {
_otel_tag_encodings[index] = 0;
}
DEBUG_ONLY(_unwind_failures.reset();)
FAULT_INJECTION_ONLY(_fi_rng = 0;)
__atomic_store_n(&_misc_flags, 0, __ATOMIC_RELEASE);
}