ART: Add ThreadStart & ThreadEnd
Add support for ThreadStart and ThreadEnd events. Add tests.
Bug: 31684920
Test: m test-art-host-run-test-924-threads
Change-Id: I516993402747ffdc9a7d66985b21b95c059be107
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index fcedd4e..be10378 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -1309,6 +1309,7 @@
PhaseUtil::SetToOnLoad();
}
PhaseUtil::Register(&gEventHandler);
+ ThreadUtil::Register(&gEventHandler);
runtime->GetJavaVM()->AddEnvironmentHook(GetEnvHandler);
runtime->AddSystemWeakHolder(&gObjectTagTable);
@@ -1316,6 +1317,13 @@
return true;
}
+extern "C" bool ArtPlugin_Deinitialize() {
+ PhaseUtil::Unregister();
+ ThreadUtil::Unregister();
+
+ return true;
+}
+
// The actual struct holding all of the entrypoints into the jvmti interface.
const jvmtiInterface_1 gJvmtiInterface = {
nullptr, // reserved1
diff --git a/runtime/openjdkjvmti/ti_phase.cc b/runtime/openjdkjvmti/ti_phase.cc
index 85d6b72..62c3ebe 100644
--- a/runtime/openjdkjvmti/ti_phase.cc
+++ b/runtime/openjdkjvmti/ti_phase.cc
@@ -125,5 +125,11 @@
art::Runtime::Current()->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&gPhaseCallback);
}
+void PhaseUtil::Unregister() {
+ art::ScopedThreadStateChange stsc(art::Thread::Current(),
+ art::ThreadState::kWaitingForDebuggerToAttach);
+ art::ScopedSuspendAll ssa("Remove phase callback");
+ art::Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&gPhaseCallback);
+}
} // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_phase.h b/runtime/openjdkjvmti/ti_phase.h
index 054652a..bd15fa6 100644
--- a/runtime/openjdkjvmti/ti_phase.h
+++ b/runtime/openjdkjvmti/ti_phase.h
@@ -44,6 +44,7 @@
static jvmtiError GetPhase(jvmtiEnv* env, jvmtiPhase* phase_ptr);
static void Register(EventHandler* event_handler);
+ static void Unregister();
// Move the phase from unitialized to LOAD.
static void SetToOnLoad();
diff --git a/runtime/openjdkjvmti/ti_thread.cc b/runtime/openjdkjvmti/ti_thread.cc
index 970cc24..bf79570 100644
--- a/runtime/openjdkjvmti/ti_thread.cc
+++ b/runtime/openjdkjvmti/ti_thread.cc
@@ -31,10 +31,12 @@
#include "ti_thread.h"
+#include "android-base/strings.h"
#include "art_field.h"
#include "art_jvmti.h"
#include "base/logging.h"
#include "base/mutex.h"
+#include "events-inl.h"
#include "gc/system_weak.h"
#include "gc_root-inl.h"
#include "jni_internal.h"
@@ -43,6 +45,8 @@
#include "mirror/string.h"
#include "obj_ptr.h"
#include "runtime.h"
+#include "runtime_callbacks.h"
+#include "ScopedLocalRef.h"
#include "scoped_thread_state_change-inl.h"
#include "thread-inl.h"
#include "thread_list.h"
@@ -50,6 +54,76 @@
namespace openjdkjvmti {
+struct ThreadCallback : public art::ThreadLifecycleCallback, public art::RuntimePhaseCallback {
+ jthread GetThreadObject(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (self->GetPeer() == nullptr) {
+ return nullptr;
+ }
+ return self->GetJniEnv()->AddLocalReference<jthread>(self->GetPeer());
+ }
+ void Post(art::Thread* self, ArtJvmtiEvent type) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ DCHECK_EQ(self, art::Thread::Current());
+ ScopedLocalRef<jthread> thread(self->GetJniEnv(), GetThreadObject(self));
+ event_handler->DispatchEvent(self, type, self->GetJniEnv(), thread.get());
+ }
+
+ void ThreadStart(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (!started) {
+ // Runtime isn't started. We only expect at most the signal handler or JIT threads to be
+ // started here.
+ if (art::kIsDebugBuild) {
+ std::string name;
+ self->GetThreadName(name);
+ if (name != "Signal Catcher" && !android::base::StartsWith(name, "Jit thread pool")) {
+ LOG(FATAL) << "Unexpected thread before start: " << name;
+ }
+ }
+ return;
+ }
+ Post(self, ArtJvmtiEvent::kThreadStart);
+ }
+
+ void ThreadDeath(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ Post(self, ArtJvmtiEvent::kThreadEnd);
+ }
+
+ void NextRuntimePhase(RuntimePhase phase) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (phase == RuntimePhase::kInit) {
+ // We moved to VMInit. Report the main thread as started (it was attached early, and must
+ // not be reported until Init.
+ started = true;
+ Post(art::Thread::Current(), ArtJvmtiEvent::kThreadStart);
+ }
+ }
+
+ EventHandler* event_handler = nullptr;
+ bool started = false;
+};
+
+ThreadCallback gThreadCallback;
+
+void ThreadUtil::Register(EventHandler* handler) {
+ art::Runtime* runtime = art::Runtime::Current();
+
+ gThreadCallback.started = runtime->IsStarted();
+ gThreadCallback.event_handler = handler;
+
+ art::ScopedThreadStateChange stsc(art::Thread::Current(),
+ art::ThreadState::kWaitingForDebuggerToAttach);
+ art::ScopedSuspendAll ssa("Add thread callback");
+ runtime->GetRuntimeCallbacks()->AddThreadLifecycleCallback(&gThreadCallback);
+ runtime->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&gThreadCallback);
+}
+
+void ThreadUtil::Unregister() {
+ art::ScopedThreadStateChange stsc(art::Thread::Current(),
+ art::ThreadState::kWaitingForDebuggerToAttach);
+ art::ScopedSuspendAll ssa("Remove thread callback");
+ art::Runtime* runtime = art::Runtime::Current();
+ runtime->GetRuntimeCallbacks()->RemoveThreadLifecycleCallback(&gThreadCallback);
+ runtime->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&gThreadCallback);
+}
+
jvmtiError ThreadUtil::GetCurrentThread(jvmtiEnv* env ATTRIBUTE_UNUSED, jthread* thread_ptr) {
art::Thread* self = art::Thread::Current();
diff --git a/runtime/openjdkjvmti/ti_thread.h b/runtime/openjdkjvmti/ti_thread.h
index 5aaec58..f6f93ee 100644
--- a/runtime/openjdkjvmti/ti_thread.h
+++ b/runtime/openjdkjvmti/ti_thread.h
@@ -37,8 +37,13 @@
namespace openjdkjvmti {
+class EventHandler;
+
class ThreadUtil {
public:
+ static void Register(EventHandler* event_handler);
+ static void Unregister();
+
static jvmtiError GetAllThreads(jvmtiEnv* env, jint* threads_count_ptr, jthread** threads_ptr);
static jvmtiError GetCurrentThread(jvmtiEnv* env, jthread* thread_ptr);
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 4936a2f..1218a98 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -735,6 +735,13 @@
GetInstructionSetString(kRuntimeISA));
}
+ // Send the initialized phase event. Send it before starting daemons, as otherwise
+ // sending thread events becomes complicated.
+ {
+ ScopedObjectAccess soa(self);
+ callbacks_->NextRuntimePhase(RuntimePhaseCallback::RuntimePhase::kInit);
+ }
+
StartDaemonThreads();
{
@@ -756,12 +763,6 @@
0);
}
- // Send the initialized phase event.
- {
- ScopedObjectAccess soa(self);
- callbacks_->NextRuntimePhase(RuntimePhaseCallback::RuntimePhase::kInit);
- }
-
return true;
}
diff --git a/test/924-threads/expected.txt b/test/924-threads/expected.txt
index 3b7fb24..67d20eb 100644
--- a/test/924-threads/expected.txt
+++ b/test/924-threads/expected.txt
@@ -31,3 +31,7 @@
[Thread[FinalizerDaemon,5,system], Thread[FinalizerWatchdogDaemon,5,system], Thread[HeapTaskDaemon,5,system], Thread[ReferenceQueueDaemon,5,system], Thread[Signal Catcher,5,system], Thread[main,5,main]]
JVMTI_ERROR_THREAD_NOT_ALIVE
JVMTI_ERROR_THREAD_NOT_ALIVE
+Constructed thread
+Thread(EventTestThread): start
+Thread(EventTestThread): end
+Thread joined
diff --git a/test/924-threads/src/Main.java b/test/924-threads/src/Main.java
index 58695f7..29c4aa3 100644
--- a/test/924-threads/src/Main.java
+++ b/test/924-threads/src/Main.java
@@ -56,6 +56,8 @@
doAllThreadsTests();
doTLSTests();
+
+ doTestEvents();
}
private static class Holder {
@@ -226,6 +228,22 @@
}
}
+ private static void doTestEvents() throws Exception {
+ enableThreadEvents(true);
+
+ Thread t = new Thread("EventTestThread");
+
+ System.out.println("Constructed thread");
+ Thread.yield();
+
+ t.start();
+ t.join();
+
+ System.out.println("Thread joined");
+
+ enableThreadEvents(false);
+ }
+
private final static Comparator<Thread> THREAD_COMP = new Comparator<Thread>() {
public int compare(Thread o1, Thread o2) {
return o1.getName().compareTo(o2.getName());
@@ -293,4 +311,5 @@
private static native Thread[] getAllThreads();
private static native void setTLS(Thread t, long l);
private static native long getTLS(Thread t);
+ private static native void enableThreadEvents(boolean b);
}
diff --git a/test/924-threads/threads.cc b/test/924-threads/threads.cc
index d35eaa8..0380433 100644
--- a/test/924-threads/threads.cc
+++ b/test/924-threads/threads.cc
@@ -137,5 +137,71 @@
JvmtiErrorToException(env, result);
}
+static void JNICALL ThreadEvent(jvmtiEnv* jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread,
+ bool is_start) {
+ jvmtiThreadInfo info;
+ jvmtiError result = jvmti_env->GetThreadInfo(thread, &info);
+ if (result != JVMTI_ERROR_NONE) {
+ printf("Error getting thread info");
+ return;
+ }
+ printf("Thread(%s): %s\n", info.name, is_start ? "start" : "end");
+
+ jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(info.name));
+ jni_env->DeleteLocalRef(info.thread_group);
+ jni_env->DeleteLocalRef(info.context_class_loader);
+}
+
+static void JNICALL ThreadStart(jvmtiEnv* jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread) {
+ ThreadEvent(jvmti_env, jni_env, thread, true);
+}
+
+static void JNICALL ThreadEnd(jvmtiEnv* jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread) {
+ ThreadEvent(jvmti_env, jni_env, thread, false);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_enableThreadEvents(
+ JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean b) {
+ if (b == JNI_FALSE) {
+ jvmtiError ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+ JVMTI_EVENT_THREAD_START,
+ nullptr);
+ if (JvmtiErrorToException(env, ret)) {
+ return;
+ }
+ ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+ JVMTI_EVENT_THREAD_END,
+ nullptr);
+ JvmtiErrorToException(env, ret);
+ return;
+ }
+
+ jvmtiEventCallbacks callbacks;
+ memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));
+ callbacks.ThreadStart = ThreadStart;
+ callbacks.ThreadEnd = ThreadEnd;
+ jvmtiError ret = jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks));
+ if (JvmtiErrorToException(env, ret)) {
+ return;
+ }
+
+ ret = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+ JVMTI_EVENT_THREAD_START,
+ nullptr);
+ if (JvmtiErrorToException(env, ret)) {
+ return;
+ }
+ ret = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+ JVMTI_EVENT_THREAD_END,
+ nullptr);
+ JvmtiErrorToException(env, ret);
+}
+
} // namespace Test924Threads
} // namespace art