Skip to content

Commit 7e09851

Browse files
Better handling for attach & de-attach & edge cases for stop on a native thread
1 parent 6cf0810 commit 7e09851

File tree

9 files changed

+169
-45
lines changed

9 files changed

+169
-45
lines changed

src/asprof.cpp

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,32 +32,21 @@ DLLEXPORT asprof_error_t asprof_execute(const char* command, asprof_writer_t out
3232

3333
Log::open(args);
3434

35-
bool attached = false;
36-
if (args._action <= ACTION_CHECK && VM::loaded() && VM::jni() == NULL) {
37-
if (VM::attachThread() == NULL) {
38-
return asprof_error("Could not attach to the JVM");
39-
}
40-
attached = true;
41-
}
42-
4335
if (!args.hasOutputFile()) {
4436
CallbackWriter out(output_callback);
4537
error = Profiler::instance()->runInternal(args, out);
38+
if (!error) {
39+
return NULL;
40+
}
4641
} else {
4742
FileWriter out(args.file());
4843
if (!out.is_open()) {
49-
error = Error("Could not open output file");
50-
} else {
51-
error = Profiler::instance()->runInternal(args, out);
44+
return asprof_error("Could not open output file");
45+
}
46+
error = Profiler::instance()->runInternal(args, out);
47+
if (!error) {
48+
return NULL;
5249
}
53-
}
54-
55-
if (attached) {
56-
VM::detachThread();
57-
}
58-
59-
if (!error) {
60-
return NULL;
6150
}
6251

6352
return asprof_error(error.message());

src/flightRecorder.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,7 @@ class Recording {
745745
}
746746

747747
void writeJvmInfo(Buffer* buf) {
748-
if (_agent_properties == NULL && !(VM::loaded() && parseAgentProperties())) {
748+
if (_agent_properties == NULL && !(VMCapabilities::available() && parseAgentProperties())) {
749749
return;
750750
}
751751

src/profiler.cpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,7 +1038,7 @@ Engine* Profiler::activeEngine() {
10381038
}
10391039

10401040
Error Profiler::checkJvmCapabilities() {
1041-
if (VM::loaded()) {
1041+
if (VMCapabilities::available()) {
10421042
if (!VMStructs::hasJavaThreadId()) {
10431043
return Error("Could not find Thread ID field. Unsupported JVM?");
10441044
}
@@ -1068,9 +1068,10 @@ Error Profiler::start(Arguments& args, bool reset) {
10681068
return Error("Profiler already started");
10691069
}
10701070

1071+
VMCapabilities vm_capabilities;
10711072
// If profiler is started from a native app, try to detect a running JVM and attach to it
10721073
if (!VM::loaded()) {
1073-
VM::tryAttach();
1074+
vm_capabilities.tryAttach();
10741075
}
10751076

10761077
Error error = checkJvmCapabilities();
@@ -1084,6 +1085,8 @@ Error Profiler::start(Arguments& args, bool reset) {
10841085
return Error("No profiling events specified");
10851086
} else if ((_event_mask & (_event_mask - 1)) && args._output != OUTPUT_JFR) {
10861087
return Error("Only JFR output supports multiple events");
1088+
} else if (VM::loaded() && !VMCapabilities::available() && (_event_mask & (EM_ALLOC | EM_LOCK))) {
1089+
return Error("Could not attach to the JVM");
10871090
} else if (!VM::loaded() && (_event_mask & (EM_ALLOC | EM_LOCK))) {
10881091
return Error("Profiling event is not supported with non-Java processes");
10891092
}
@@ -1166,6 +1169,8 @@ Error Profiler::start(Arguments& args, bool reset) {
11661169
return Error("target-cpu is only supported with perf_events");
11671170
} else if (_engine != &perf_events && args._record_cpu) {
11681171
return Error("record-cpu is only supported with perf_events");
1172+
} else if (_engine == &instrument && VM::loaded() && !vm_capabilities.available()) {
1173+
return Error("Could not attach to the JVM");
11691174
}
11701175

11711176
_cstack = args._cstack;
@@ -1242,6 +1247,7 @@ Error Profiler::start(Arguments& args, bool reset) {
12421247
startTimer();
12431248
}
12441249

1250+
_started_with_vm = vm_capabilities.available();
12451251
return Error::OK;
12461252

12471253
error5:
@@ -1274,6 +1280,11 @@ Error Profiler::stop(bool restart) {
12741280
return Error("Profiler is not active");
12751281
}
12761282

1283+
VMCapabilities vm_capabilities;
1284+
if (_started_with_vm && !vm_capabilities.available()) {
1285+
return Error("Profiler started with VM, unable to attach to VM, couldn't stop");
1286+
}
1287+
12771288
uninstallTraps();
12781289

12791290
if (_event_mask & EM_WALL) wall_clock.stop();
@@ -1313,6 +1324,7 @@ Error Profiler::check(Arguments& args) {
13131324
return Error("Profiler already started");
13141325
}
13151326

1327+
VMCapabilities vm_capabilities;
13161328
Error error = checkJvmCapabilities();
13171329

13181330
if (!error && args._event != NULL) {
@@ -1369,6 +1381,8 @@ Error Profiler::dump(Writer& out, Arguments& args) {
13691381
return Error("Profiler has not started");
13701382
}
13711383

1384+
VMCapabilities vm_capabilities;
1385+
13721386
if (_state == RUNNING) {
13731387
updateJavaThreadNames();
13741388
updateNativeThreadNames();
@@ -1803,7 +1817,7 @@ u64 Profiler::addTimeout(u64 start_micros, int timeout) {
18031817
}
18041818

18051819
void Profiler::startTimer() {
1806-
if (VM::loaded()) {
1820+
if (VMCapabilities::available()) {
18071821
JNIEnv* jni = VM::jni();
18081822
jclass Thread = jni->FindClass("java/lang/Thread");
18091823
jmethodID init = jni->GetMethodID(Thread, "<init>", "(Ljava/lang/String;)V");

src/profiler.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class Profiler {
5151
private:
5252
Mutex _state_lock;
5353
State _state;
54+
bool _started_with_vm;
5455
Trap _begin_trap;
5556
Trap _end_trap;
5657
bool _nostop;
@@ -157,6 +158,7 @@ class Profiler {
157158
public:
158159
Profiler() :
159160
_state(NEW),
161+
_started_with_vm(true),
160162
_begin_trap(2),
161163
_end_trap(3),
162164
_thread_filter(),

src/tsc.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ void TSC::enable(Clock clock) {
2121
}
2222

2323
if (!_initialized) {
24-
if (VM::loaded()) {
24+
if (VMCapabilities::available()) {
2525
JNIEnv* env = VM::jni();
2626

2727
jfieldID jvm;

src/vmEntry.cpp

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ int VM::_hotspot_version = 0;
3232
bool VM::_openj9 = false;
3333
bool VM::_zing = false;
3434

35-
GetCreatedJavaVMs VM::_getCreatedJavaVMs = NULL;
35+
GetCreatedJavaVMs VMCapabilities::_getCreatedJavaVMs = NULL;
3636

3737
jvmtiError (JNICALL *VM::_orig_RedefineClasses)(jvmtiEnv*, jint, const jvmtiClassDefinition*);
3838
jvmtiError (JNICALL *VM::_orig_RetransformClasses)(jvmtiEnv*, jint, const jclass* classes);
@@ -110,7 +110,7 @@ static void* resolveMethodIdEnd() {
110110

111111
// Workaround for JDK-8308341: since JNI_GetCreatedJavaVMs may return an uninitialized JVM,
112112
// we verify the readiness of the JVM by presence of "VM Thread" and "Service Thread".
113-
bool VM::hasJvmThreads() {
113+
bool VMCapabilities::hasJvmThreads() {
114114
char thread_name[32];
115115
int threads_found = 0;
116116

@@ -302,7 +302,7 @@ bool VM::init(JavaVM* vm, bool attach) {
302302
}
303303

304304
// Try to find a running JVM instance and attach to it
305-
void VM::tryAttach() {
305+
void VMCapabilities::tryAttach() {
306306
if (_getCreatedJavaVMs == NULL) {
307307
void* lib_handle = dlopen(OS::isLinux() ? "libjvm.so" : "libjvm.dylib", RTLD_LAZY | RTLD_NOLOAD);
308308
if (lib_handle != NULL) {
@@ -324,11 +324,16 @@ void VM::tryAttach() {
324324
jint get_env_result = vm->GetEnv((void**)&env, JNI_VERSION_1_6);
325325
if (get_env_result == JNI_OK) {
326326
// Current thread already belongs to the running JVM
327-
VM::init(vm, true);
327+
if (VM::init(vm, true)) {
328+
_available = true;
329+
}
328330
} else if (get_env_result == JNI_EDETACHED) {
329331
// There is a running JVM, but we need to check it is initialized
330332
if (hasJvmThreads() && vm->AttachCurrentThreadAsDaemon((void**)&env, NULL) == JNI_OK) {
331-
VM::init(vm, true);
333+
if (VM::init(vm, true)) {
334+
_available = true;
335+
_attached = true;
336+
}
332337
}
333338
}
334339
}
@@ -454,6 +459,36 @@ jvmtiError VM::RetransformClassesHook(jvmtiEnv* jvmti, jint class_count, const j
454459
return result;
455460
}
456461

462+
thread_local bool VMCapabilities::_available = false;
463+
464+
VMCapabilities::VMCapabilities() : _attached(false) {
465+
_available = false;
466+
467+
if (!VM::loaded()) {
468+
return;
469+
}
470+
471+
if (VM::jni()) {
472+
_available = true;
473+
return;
474+
}
475+
476+
if (VM::attachThread()) {
477+
_attached = true;
478+
_available = true;
479+
return;
480+
}
481+
}
482+
483+
VMCapabilities::~VMCapabilities() {
484+
_available = false;
485+
486+
if (_attached) {
487+
VM::detachThread();
488+
}
489+
}
490+
491+
457492
extern "C" DLLEXPORT jint JNICALL
458493
Agent_OnLoad(JavaVM* vm, char* options, void* reserved) {
459494
if (!_global_args._preloaded) {

src/vmEntry.h

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,16 +105,13 @@ class VM {
105105
static bool _openj9;
106106
static bool _zing;
107107

108-
static GetCreatedJavaVMs _getCreatedJavaVMs;
109-
110108
static jvmtiError (JNICALL *_orig_RedefineClasses)(jvmtiEnv*, jint, const jvmtiClassDefinition*);
111109
static jvmtiError (JNICALL *_orig_RetransformClasses)(jvmtiEnv*, jint, const jclass* classes);
112110

113111
static void ready();
114112
static void applyPatch(char* func, const char* patch, const char* end_patch);
115113
static void loadMethodIDs(jvmtiEnv* jvmti, JNIEnv* jni, jclass klass);
116114
static void loadAllMethodIDs(jvmtiEnv* jvmti, JNIEnv* jni);
117-
static bool hasJvmThreads();
118115

119116
public:
120117
static AsyncGetCallTrace _asyncGetCallTrace;
@@ -123,8 +120,6 @@ class VM {
123120

124121
static bool init(JavaVM* vm, bool attach);
125122

126-
static void tryAttach();
127-
128123
static bool loaded() {
129124
return _jvmti != NULL;
130125
}
@@ -192,4 +187,23 @@ class VM {
192187
static jvmtiError JNICALL RetransformClassesHook(jvmtiEnv* jvmti, jint class_count, const jclass* classes);
193188
};
194189

190+
class VMCapabilities {
191+
private:
192+
static GetCreatedJavaVMs _getCreatedJavaVMs;
193+
static thread_local bool _available;
194+
bool _attached;
195+
196+
static bool hasJvmThreads();
197+
198+
public:
199+
VMCapabilities();
200+
~VMCapabilities();
201+
202+
void tryAttach();
203+
204+
static bool available() {
205+
return _available;
206+
}
207+
};
208+
195209
#endif // _VMENTRY_H

test/test/nonjava/NonjavaTests.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,15 @@ public void startDifferentThread(TestProcess p) throws Exception {
7575
Output out = p.readFile("%profile");
7676
assert out.contains("cpuHeavyTask");
7777
}
78+
79+
// Profile is re-started on a different native thread from the one that started the JVM/profiler,
80+
// and profiler is stopped on a 3rd different native thread
81+
@Test(sh = "%testbin/non_java_app 7 %profile.jfr", output = true)
82+
public void startStopDifferentThreadsJfr(TestProcess p) throws Exception {
83+
p.waitForExit();
84+
assert p.exitCode() == 0;
85+
86+
Output out = Output.convertJfrToCollapsed(p.getFilePath("%profile"));
87+
assert out.contains("cpuHeavyTask");
88+
}
7889
}

0 commit comments

Comments
 (0)