Skip to content

Commit 99a76e0

Browse files
async-profiler#1174: Add handling for JVM being created on a different thread than the async-profiler
1 parent 70f651d commit 99a76e0

File tree

5 files changed

+102
-4
lines changed

5 files changed

+102
-4
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ build-test-bins:
210210
@mkdir -p $(TEST_BIN_DIR)
211211
gcc -o $(TEST_BIN_DIR)/malloc_plt_dyn test/test/nativemem/malloc_plt_dyn.c
212212
gcc -o $(TEST_BIN_DIR)/native_api -Isrc test/test/c/native_api.c -ldl
213-
$(CXX) -o $(TEST_BIN_DIR)/non_java_app $(CPP_TEST_INCLUDES) $(INCLUDES) test/test/nonjava/non_java_app.cpp -ldl
213+
$(CXX) -o $(TEST_BIN_DIR)/non_java_app $(CPP_TEST_INCLUDES) $(INCLUDES) test/test/nonjava/non_java_app.cpp -ldl -pthread
214214

215215
test-cpp: build-test-cpp
216216
echo "Running cpp tests..."

src/vmEntry.cpp

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,43 @@ static void* resolveMethodIdEnd() {
106106
return NULL;
107107
}
108108

109+
void VM::initWrapper(JavaVM* vm) {
110+
JNIEnv* env;
111+
112+
jint get_env_result = vm->GetEnv((void**)&env, JNI_VERSION_1_6);
113+
if (get_env_result == JNI_OK) {
114+
VM::init(vm, true);
115+
return;
116+
} else if (get_env_result != JNI_EDETACHED) {
117+
return;
118+
}
119+
120+
// VM exists but not attached to current thread
121+
122+
// Work around for JDK-8308341, as JNI might return a none initialized VM so we check for the existence of certain VM threads
123+
// "VM Thread" and "Service Thread" in this case are used as indication that the VM is in a good enough state to be used
124+
char thread_name[4096];
125+
bool vm_thread = false, service_thread = false;
126+
127+
ThreadList* list = OS::listThreads();
128+
while (list->hasNext()) {
129+
if (!OS::threadName(list->next(), thread_name, 4096)) {
130+
continue;
131+
}
132+
133+
if (thread_name[0] == 'V' && strcmp(thread_name, "VM Thread") == 0) {
134+
vm_thread = true;
135+
} else if (thread_name[0] == 'S' && strcmp(thread_name, "Service Thread") == 0) {
136+
service_thread = true;
137+
}
138+
}
139+
140+
// Check that VM is in a good state to attach the thread
141+
if (service_thread && vm_thread && vm->AttachCurrentThreadAsDaemon((void**)&env, NULL) == JNI_OK) {
142+
VM::init(vm, true);
143+
return;
144+
}
145+
}
109146

110147
bool VM::init(JavaVM* vm, bool attach) {
111148
if (_jvmti != NULL) return true;
@@ -487,5 +524,5 @@ void VM::tryAttach() {
487524
if (result != JNI_OK || nVMs != 1) {
488525
return;
489526
}
490-
VM::init(jvm, true);
527+
VM::initWrapper(jvm);
491528
}

src/vmEntry.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ class VM {
113113
static void loadMethodIDs(jvmtiEnv* jvmti, JNIEnv* jni, jclass klass);
114114
static void loadAllMethodIDs(jvmtiEnv* jvmti, JNIEnv* jni);
115115

116+
static void initWrapper(JavaVM* vm);
117+
116118
public:
117119
static AsyncGetCallTrace _asyncGetCallTrace;
118120
static JVM_MemoryFunc _totalMemory;

test/test/nonjava/NonjavaTests.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,14 @@ public void jvmInBetweenTest(TestProcess p) throws Exception {
4343
out = p.readFile("%s");
4444
assert out.contains(".cpuHeavyTask");
4545
}
46+
47+
// jvm is loaded before the profiling session is started on a different thread
48+
@Test(sh = "%testbin/non_java_app 4 %s.html", output = true)
49+
public void differentThread(TestProcess p) throws Exception {
50+
Output out = p.waitForExit(TestProcess.STDOUT);
51+
assert p.exitCode() == 0;
52+
53+
out = p.readFile("%s");
54+
assert out.contains(".cpuHeavyTask");
55+
}
4656
}

test/test/nonjava/non_java_app.cpp

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <iostream>
1010
#include <limits.h>
1111
#include "asprof.h"
12+
#include <pthread.h>
1213

1314
// detect arch for java 8
1415
#if defined(__x86_64__) || defined(_M_X64)
@@ -266,7 +267,7 @@ The JVM is started after the profiling session is started so it's not attached c
266267
However the second profiling session is started after the JVM is started so it's attached correctly at the session start
267268
*/
268269
void testFlow3(int argc, char** argv) {
269-
validateArgsCount(argc, 4, "Test requires 4 arguments");
270+
validateArgsCount(argc, 4, "Test requires 3 arguments");
270271

271272
loadProfiler();
272273

@@ -289,8 +290,53 @@ void testFlow3(int argc, char** argv) {
289290
stopJvm();
290291
}
291292

293+
void* jvmThreadWrapper(void* arg) {
294+
loadJvmLib();
295+
296+
startJvm();
297+
298+
while(1) executeJvmTask();
299+
300+
return NULL;
301+
}
302+
303+
/*
304+
Here is the flow of the test:
305+
1. Load the profiler
306+
2. Load the JVM library
307+
3. Start the JVM on a different thread
308+
4. Start the profiler
309+
5. Execute the JVM task
310+
6. Stop the profiler
311+
7. Stop the JVM
312+
313+
Expected output:
314+
The profiler should be able to profile the JVM task
315+
316+
Explaination:
317+
The JVM is loaded and started before the profiling session is started so it's attached correctly at the session start
318+
*/
319+
void testFlow4(int argc, char** argv) {
320+
validateArgsCount(argc, 3, "Minimum Arguments is 2");
321+
322+
loadProfiler();
323+
324+
pthread_t thread;
325+
pthread_create(&thread, NULL, jvmThreadWrapper, NULL);
326+
327+
// busy wait for JVM to start on the thread
328+
for (int i = 0; i < 10; i++) for (int i = 0; i < 1000000000; ++i) {}
329+
330+
startProfiler();
331+
332+
// busy wait for profiler to sample JVM
333+
for (int i = 0; i < 10; i++) for (int i = 0; i < 1000000000; ++i) {}
334+
335+
stopProfiler(argv[2]);
336+
}
337+
292338
int main(int argc, char** argv) {
293-
validateArgsCount(argc, 3, "Minimum Arguments is 3");
339+
validateArgsCount(argc, 3, "Minimum Arguments is 2");
294340

295341
// Check which test to run
296342
char* flow = argv[1];
@@ -304,6 +350,9 @@ int main(int argc, char** argv) {
304350
case '3':
305351
testFlow3(argc, argv);
306352
break;
353+
case '4':
354+
testFlow4(argc, argv);
355+
break;
307356
default:
308357
std::cerr << "Unknown flow: " << flow[0] << std::endl;
309358
exit(1);

0 commit comments

Comments
 (0)