Skip to content

Commit 1b077af

Browse files
committed
v3
1 parent 125fbb0 commit 1b077af

7 files changed

Lines changed: 141 additions & 46 deletions

File tree

ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ static jmethodID getMethodId(VMMethod* method) {
4040
return NULL;
4141
}
4242

43+
static ThreadLocal<jmp_buf*> jmp_ctx;
44+
45+
void HotspotSupport::initThread() {
46+
// Ensure the slot is allocated
47+
jmp_ctx.set(nullptr);
48+
}
49+
4350
/**
4451
* Converts a BCI_* frame type value to the corresponding EventType enum value.
4552
*
@@ -146,8 +153,6 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex
146153
}
147154
}
148155

149-
static ThreadLocal<jmp_buf*> jmp_ctx;
150-
151156
__attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontext, ASGCT_CallFrame* frames, int max_depth,
152157
StackWalkFeatures features, EventType event_type,
153158
const void* pc, uintptr_t sp, uintptr_t fp, int lock_index, bool* truncated) {

ddprof-lib/src/main/cpp/hotspot/hotspotSupport.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ class HotspotSupport {
3434
return HotspotStackFrame::unwindAtomicStub(frame, pc);
3535
}
3636

37+
// Per-thread initialization.
38+
// *Must* be called before signal is enabled for the thread
39+
static void initThread();
40+
3741
static inline bool isJitCode(const void* p) {
3842
return JitCodeCache::isJitCode(p);
3943
}

ddprof-lib/src/main/cpp/hotspot/vmStructs.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1212,7 +1212,7 @@ inline bool crashProtectionActive() {
12121212
// is equally redundant — any bad read will be caught by the SIGSEGV handler.
12131213
// Uses VMThread::isExceptionActive() which reads the field directly without
12141214
// going through at() to avoid recursive assertion.
1215-
return JVMThread::key() != pthread_key_t(-1) && VMThread::isExceptionActive();
1215+
return JVMThread::isInitialized() && VMThread::isExceptionActive();
12161216
}
12171217

12181218
#endif // _HOTSPOT_VMSTRUCTS_H

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

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,33 @@
44
*/
55

66
#include "jvmThread.h"
7+
#include "hotspot/hotspotSupport.h"
78
#include "hotspot/vmStructs.inline.h"
89
#include "j9/j9Support.h"
910
#include "zing/zingSupport.h"
1011
#include "vmEntry.h"
1112

12-
pthread_key_t JVMThread::_thread_key = pthread_key_t(-1);
1313
jfieldID JVMThread::_tid = nullptr;
14+
ThreadLocal<JVMThread*> JVMThread::_jvm_thread;
1415

1516
bool JVMThread::initialize() {
1617
void* current_thread = currentThreadSlow();
17-
if (current_thread == nullptr) {
18-
return false;
19-
}
20-
21-
for (int i = 0; i < 1024; i++) {
22-
if (pthread_getspecific((pthread_key_t)i) == current_thread) {
23-
_thread_key = pthread_key_t(i);
24-
break;
25-
}
26-
}
27-
// _tid is initialized in currentThreadSlow()
18+
_jvm_thread.initialize(current_thread);
19+
// _tid is side-effect of currentThreadSlow()
2820
assert(_tid != nullptr);
29-
return _thread_key != pthread_key_t(-1);
21+
return _jvm_thread.isKeyValid();
22+
}
23+
24+
/**
25+
* Per-thread initialization
26+
*
27+
* This method *must* be called before signal is enabled
28+
* for the thread.
29+
*/
30+
void JVMThread::initThread() {
31+
if (VM::isHotspot()) {
32+
HotspotSupport::initThread();
33+
}
3034
}
3135

3236
int JVMThread::nativeThreadId(JNIEnv* jni, jthread thread) {

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,48 +6,48 @@
66
#ifndef _JVMTHREAD_H
77
#define _JVMTHREAD_H
88

9-
#include <cassert>
109
#include <jni.h>
1110
#include <jvmti.h>
12-
#include <pthread.h>
11+
12+
#include "threadLocal.h"
1313

1414
/**
1515
* JVMThread represents a native JVM thread that is JVM implementation agnostic
1616
*/
1717
class JVMThread {
1818
private:
19-
static pthread_key_t _thread_key;
2019
static jfieldID _tid;
20+
static ThreadLocal<JVMThread*> _jvm_thread;
2121

2222
public:
23-
static bool isInitialized() {
24-
return _thread_key != pthread_key_t(-1);
25-
}
26-
2723
/*
2824
* The initialization happens in early startup, in single-threaded mode,
2925
* no synchronization is needed
3026
*/
3127
static bool initialize();
28+
static bool isInitialized() {
29+
return _tid != nullptr && _jvm_thread.isKeyValid();
30+
}
31+
32+
// Per-thread initialization
33+
static void initThread();
34+
3235
static inline void* current() {
33-
assert(isInitialized());
34-
return pthread_getspecific(_thread_key);
36+
return _jvm_thread.get();
3537
}
3638

3739
static inline pthread_key_t key() {
38-
return _thread_key;
40+
return _jvm_thread.key();
3941
}
4042

4143
static int nativeThreadId(JNIEnv* jni, jthread thread);
42-
4344
static inline jlong javaThreadId(JNIEnv* env, jthread thread) {
4445
return env->GetLongField(thread, _tid);
4546
}
4647

4748
static inline bool hasJavaThreadId() {
4849
return _tid != nullptr;
4950
}
50-
5151
private:
5252
static void* currentThreadSlow();
5353
};

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#ifdef __linux__
99
#include "counters.h"
1010
#include "guards.h"
11+
#include "jvmThread.h"
1112
#include "nativeSocketSampler.h"
1213
#include "profiler.h"
1314

@@ -258,6 +259,7 @@ static void init_tls_and_register() {
258259
pt->startInitWindow();
259260
}
260261
Profiler::registerThread(ProfiledThread::currentTid());
262+
JVMThread::initThread();
261263
}
262264

263265
// Wrapper around the real start routine.
@@ -373,6 +375,7 @@ static void* start_routine_wrapper(void* args) {
373375
ProfiledThread::initCurrentThread();
374376
ProfiledThread::currentSignalSafe()->startInitWindow();
375377
Profiler::registerThread(ProfiledThread::currentTid());
378+
JVMThread::initThread();
376379
}
377380
// Use POSIX cleanup instead of C++ RAII to handle pthread_exit(): see run_with_cleanup.
378381
// cleanup_unregister has already run on run_with_cleanup's normal return path.

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

Lines changed: 97 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@
1616
* Due to some restrictions of the language implementations, especially, on musl/aarch64,
1717
* they cannot be safely used in profiler.
1818
*
19+
* pthread_(get/set)specific() are not async-signal-safe, according to
20+
* https://man7.org/linux/man-pages/man7/signal-safety.7.html
21+
*
22+
* In POSIX implementation, pthread_setspecific() call can trigger memory allocation
23+
* if the slot is not available.
24+
* Because we depend on the APIs to maintain per-thread data, we need to workaround the
25+
* problem - call pthread_setspecific() at least once before signal is enabled for the
26+
* thread (ideally, the value is set before signal is enabled).
27+
*
1928
* How to use?
2029
* A ThreadLocal should be declared as a static variable, e.g.
2130
*
@@ -40,52 +49,60 @@
4049
*
4150
*/
4251

52+
#include <unistd.h>
53+
4354
// The function to create value if it does not exist
4455
typedef void* (*CREATE_FUNC)(void);
4556
// Cleanup the value when deleting the key
4657
typedef void (*CLEAN_FUNC)(void*);
58+
59+
static constexpr pthread_key_t INVLID_KEY = pthread_key_t(-1);
60+
4761
template <typename T, CREATE_FUNC C = nullptr, CLEAN_FUNC F = nullptr>
4862
class ThreadLocal {
4963
protected:
5064
pthread_key_t _key;
51-
bool _key_valid;
5265

5366
public:
5467
ThreadLocal(const ThreadLocal&) = delete;
5568
ThreadLocal& operator=(const ThreadLocal&) = delete;
5669

57-
ThreadLocal() {
70+
ThreadLocal() : _key(INVLID_KEY) {
5871
static_assert(sizeof(T) == sizeof(void*),
5972
"ThreadLocal<T> requires sizeof(T)==sizeof(void*); use a pointer type or add a specialization");
60-
_key_valid = pthread_key_create(&_key, F) == 0;
73+
pthread_key_create(&_key, F);
6174
// What to do if we can not create a key?
6275
// We probably want to shutdown profiler gracefully, instead of
6376
// aborting user application - We will need this mechanism globally,
6477
// defer to a separate task.
65-
assert(_key_valid);
78+
assert(isKeyValid());
6679
}
6780

6881
~ThreadLocal() {
69-
if(_key_valid) {
82+
if(isKeyValid()) {
7083
pthread_key_delete(_key);
7184
} else {
7285
assert(false && "Invalid pthread key");
7386
}
7487
}
7588

89+
bool isKeyValid() const {
90+
return _key != INVLID_KEY;
91+
}
92+
7693
/**
7794
* set(nullptr) will result in the value being recreated when get() is called
7895
* when CREATE_FUNC is not nullptr.
7996
* Note: caller is responsible to free old value, which mirrors thread_local
8097
*/
8198
void set(T value) {
82-
assert(_key_valid && "Invalid pthread key");
99+
assert(isKeyValid() && "Invalid pthread key");
83100
int err = pthread_setspecific(_key, reinterpret_cast<const void*>(value));
84101
assert(err == 0);
85102
}
86103

87104
T get() {
88-
assert(_key_valid && "Invalid pthread key");
105+
assert(isKeyValid() && "Invalid pthread key");
89106
void* p = pthread_getspecific(_key);
90107
if (p == nullptr && C != nullptr) {
91108
p = C();
@@ -96,7 +113,7 @@ class ThreadLocal {
96113

97114
// Clear the value
98115
void clear() {
99-
assert(_key_valid && "Invalid pthread key");
116+
assert(isKeyValid() && "Invalid pthread key");
100117
void* p = pthread_getspecific(_key);
101118
if (p == nullptr) return;
102119
int err = pthread_setspecific(_key, nullptr);
@@ -112,41 +129,44 @@ template <>
112129
class ThreadLocal<double> {
113130
protected:
114131
pthread_key_t _key;
115-
bool _key_valid;
116132

117133
public:
118134
ThreadLocal(const ThreadLocal&) = delete;
119135
ThreadLocal& operator=(const ThreadLocal&) = delete;
120136

121-
ThreadLocal() {
137+
ThreadLocal() : _key(INVLID_KEY) {
122138
// Only support 64-bit platforms, double and void* are the same size
123139
static_assert(sizeof(void*) == 8);
124140
static_assert(sizeof(double) == 8);
125-
_key_valid = pthread_key_create(&_key, nullptr) == 0;
141+
pthread_key_create(&_key, nullptr);
126142
// What to do if we can not create a key?
127-
assert(_key_valid && "Invalid pthread key");
143+
assert(isKeyValid() && "Invalid pthread key");
128144
}
129145

130146
~ThreadLocal() {
131-
if(_key_valid) {
147+
if(isKeyValid()) {
132148
pthread_key_delete(_key);
133149
} else {
134-
assert(_key_valid && "Invalid pthread key");
150+
assert(isKeyValid() && "Invalid pthread key");
135151
}
136152
}
137153

154+
bool isKeyValid() const {
155+
return _key != INVLID_KEY;
156+
}
157+
138158
// double <--> u64 cast, preserve bit format
139159
// Can use std::bit_cast after upgrade C++ version to 20
140160
void set(double value) {
141-
assert(_key_valid && "Invalid pthread key");
161+
assert(isKeyValid() && "Invalid pthread key");
142162
u64 val;
143163
memcpy(&val, &value, sizeof(value));
144164
int err = pthread_setspecific(_key, reinterpret_cast<const void*>(val));
145165
assert(err == 0);
146166
}
147167

148-
double get() {
149-
assert(_key_valid && "Invalid pthread key");
168+
double get() const {
169+
assert(isKeyValid() && "Invalid pthread key");
150170
void* p = pthread_getspecific(_key);
151171
if (p == nullptr) {
152172
return 0.0;
@@ -159,10 +179,69 @@ class ThreadLocal<double> {
159179
}
160180

161181
void clear() {
162-
assert(_key_valid && "Invalid pthread key");
182+
assert(isKeyValid() && "Invalid pthread key");
163183
int err = pthread_setspecific(_key, nullptr);
164184
assert(err == 0);
165185
}
166186
};
167187

188+
class JVMThread;
189+
190+
/**
191+
* This thread local mirrors JVM's Thread::current(). The value is set by JVM
192+
* and it is read-only variable.
193+
*/
194+
template <>
195+
class ThreadLocal<JVMThread*> {
196+
protected:
197+
pthread_key_t _key;
198+
199+
public:
200+
ThreadLocal(const ThreadLocal&) = delete;
201+
ThreadLocal& operator=(const ThreadLocal&) = delete;
202+
203+
ThreadLocal() : _key(INVLID_KEY) {
204+
}
205+
206+
void initialize(void* current_thread) {
207+
// Called from known JavaThread, it should never be nullptr.
208+
if (current_thread == nullptr) {
209+
assert(false && "Should not reach here");
210+
}
211+
212+
long max_keys = sysconf(_SC_THREAD_KEYS_MAX);
213+
214+
for (long i = 0; i < max_keys; i++) {
215+
if (pthread_getspecific((pthread_key_t)i) == current_thread) {
216+
_key = pthread_key_t(i);
217+
break;
218+
}
219+
}
220+
221+
assert(isKeyValid() && "Invalid thread key");
222+
}
223+
224+
bool isKeyValid() const {
225+
return _key != INVLID_KEY;
226+
}
227+
228+
pthread_key_t key() const {
229+
return _key;
230+
}
231+
232+
void set(JVMThread* value) {
233+
assert(false && "Should not reach here, value is set by JVM");
234+
}
235+
236+
void* get() const {
237+
assert(isKeyValid() && "Invalid pthread key");
238+
return pthread_getspecific(_key);
239+
}
240+
241+
void clear() {
242+
assert(false && "Should not reach here");
243+
}
244+
};
245+
246+
168247
#endif // _THREADLOCAL_H

0 commit comments

Comments
 (0)