Fix interaction of VMInit and ThreadStart events.
Real world agents require that one is able to create, and run, new
threads while the VMInit event is still being executed. Further, these
require that ThreadStart events can occur concurrently with the VMInit
event. This CL enables this behavior and adds a test for the
interaction of these two events.
Test: ./test.py --host -j50
Bug: 62821960
Bug: 34415266
Change-Id: I305f1ce3f1df9bf5a7e33027e0724f5fbac5c0f1
diff --git a/openjdkjvmti/generate-operator-out.py b/openjdkjvmti/generate-operator-out.py
new file mode 120000
index 0000000..cc291d2
--- /dev/null
+++ b/openjdkjvmti/generate-operator-out.py
@@ -0,0 +1 @@
+../tools/generate-operator-out.py
\ No newline at end of file
diff --git a/openjdkjvmti/ti_phase.cc b/openjdkjvmti/ti_phase.cc
index e8c1ca7..07cf31c 100644
--- a/openjdkjvmti/ti_phase.cc
+++ b/openjdkjvmti/ti_phase.cc
@@ -72,9 +72,16 @@
{
ThreadUtil::CacheData();
PhaseUtil::current_phase_ = JVMTI_PHASE_LIVE;
- ScopedLocalRef<jthread> thread(GetJniEnv(), GetCurrentJThread());
- art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative);
- event_handler->DispatchEvent<ArtJvmtiEvent::kVmInit>(nullptr, GetJniEnv(), thread.get());
+ {
+ ScopedLocalRef<jthread> thread(GetJniEnv(), GetCurrentJThread());
+ art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative);
+ event_handler->DispatchEvent<ArtJvmtiEvent::kVmInit>(
+ nullptr, GetJniEnv(), thread.get());
+ }
+ // We need to have these events be ordered to match behavior expected by some real-world
+ // agents. The spec does not really require this but compatibility is a useful property to
+ // maintain.
+ ThreadUtil::VMInitEventSent();
}
break;
case RuntimePhase::kDeath:
diff --git a/openjdkjvmti/ti_thread.cc b/openjdkjvmti/ti_thread.cc
index 6fa73f8..b0a1a85 100644
--- a/openjdkjvmti/ti_thread.cc
+++ b/openjdkjvmti/ti_thread.cc
@@ -57,13 +57,14 @@
art::ArtField* ThreadUtil::context_class_loader_ = nullptr;
-struct ThreadCallback : public art::ThreadLifecycleCallback, public art::RuntimePhaseCallback {
+struct ThreadCallback : public art::ThreadLifecycleCallback {
jthread GetThreadObject(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_) {
if (self->GetPeer() == nullptr) {
return nullptr;
}
return self->GetJniEnv()->AddLocalReference<jthread>(self->GetPeer());
}
+
template <ArtJvmtiEvent kEvent>
void Post(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_) {
DCHECK_EQ(self, art::Thread::Current());
@@ -96,15 +97,6 @@
Post<ArtJvmtiEvent::kThreadEnd>(self);
}
- 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<ArtJvmtiEvent::kThreadStart>(art::Thread::Current());
- }
- }
-
EventHandler* event_handler = nullptr;
bool started = false;
};
@@ -121,10 +113,19 @@
art::ThreadState::kWaitingForDebuggerToAttach);
art::ScopedSuspendAll ssa("Add thread callback");
runtime->GetRuntimeCallbacks()->AddThreadLifecycleCallback(&gThreadCallback);
- runtime->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&gThreadCallback);
+}
+
+void ThreadUtil::VMInitEventSent() {
+ // We should have already started.
+ DCHECK(gThreadCallback.started);
+ // We moved to VMInit. Report the main thread as started (it was attached early, and must not be
+ // reported until Init.
+ gThreadCallback.Post<ArtJvmtiEvent::kThreadStart>(art::Thread::Current());
}
void ThreadUtil::CacheData() {
+ // We must have started since it is now safe to cache our data;
+ gThreadCallback.started = true;
art::ScopedObjectAccess soa(art::Thread::Current());
art::ObjPtr<art::mirror::Class> thread_class =
soa.Decode<art::mirror::Class>(art::WellKnownClasses::java_lang_Thread);
@@ -140,7 +141,6 @@
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) {
diff --git a/openjdkjvmti/ti_thread.h b/openjdkjvmti/ti_thread.h
index 03c49d7..a19974a 100644
--- a/openjdkjvmti/ti_thread.h
+++ b/openjdkjvmti/ti_thread.h
@@ -53,9 +53,14 @@
static void Register(EventHandler* event_handler);
static void Unregister();
- // To be called when it is safe to cache data.
+ // To be called when it is safe to cache data. This means that we have at least entered the
+ // RuntimePhase::kInit but we might or might not have already called VMInit event.
static void CacheData();
+ // Called just after we have sent the VMInit callback so that ThreadUtil can do final setup. This
+ // ensures that there are no timing issues between the two callbacks.
+ static void VMInitEventSent() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
// Handle a jvmtiEnv going away.
static void RemoveEnvironment(jvmtiEnv* env);
diff --git a/test/1919-vminit-thread-start-timing/expected.txt b/test/1919-vminit-thread-start-timing/expected.txt
new file mode 100644
index 0000000..6361451
--- /dev/null
+++ b/test/1919-vminit-thread-start-timing/expected.txt
@@ -0,0 +1,4 @@
+VMInit: main
+ThreadStart: JVMTI_THREAD-Test1919
+Test1919AgentThread: JVMTI_THREAD-Test1919
+ThreadStart: main
diff --git a/test/1919-vminit-thread-start-timing/info.txt b/test/1919-vminit-thread-start-timing/info.txt
new file mode 100644
index 0000000..995f0a1
--- /dev/null
+++ b/test/1919-vminit-thread-start-timing/info.txt
@@ -0,0 +1,3 @@
+Tests basic functions in the jvmti plugin.
+
+Test the interaction of VMInit events and thread starts.
diff --git a/test/1919-vminit-thread-start-timing/run b/test/1919-vminit-thread-start-timing/run
new file mode 100755
index 0000000..c6e62ae
--- /dev/null
+++ b/test/1919-vminit-thread-start-timing/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-run "$@" --jvmti
diff --git a/test/1919-vminit-thread-start-timing/src/Main.java b/test/1919-vminit-thread-start-timing/src/Main.java
new file mode 100644
index 0000000..65781b8
--- /dev/null
+++ b/test/1919-vminit-thread-start-timing/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+ public static void main(String[] args) {
+ art.Test1919.run();
+ }
+}
diff --git a/test/1919-vminit-thread-start-timing/src/art/Main.java b/test/1919-vminit-thread-start-timing/src/art/Main.java
new file mode 100644
index 0000000..8b01920
--- /dev/null
+++ b/test/1919-vminit-thread-start-timing/src/art/Main.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+// Binder class so the agent's C code has something that can be bound and exposed to tests.
+// In a package to separate cleanly and work around CTS reference issues (though this class
+// should be replaced in the CTS version).
+public class Main {
+ // Load the given class with the given classloader, and bind all native methods to corresponding
+ // C methods in the agent. Will abort if any of the steps fail.
+ public static native void bindAgentJNI(String className, ClassLoader classLoader);
+ // Same as above, giving the class directly.
+ public static native void bindAgentJNIForClass(Class<?> klass);
+}
diff --git a/test/1919-vminit-thread-start-timing/src/art/Test1919.java b/test/1919-vminit-thread-start-timing/src/art/Test1919.java
new file mode 100644
index 0000000..3d5c079
--- /dev/null
+++ b/test/1919-vminit-thread-start-timing/src/art/Test1919.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+public class Test1919 {
+ public static final boolean PRINT_ALL_THREADS = false;
+
+ public static void run() {
+ for (Event e : getEvents()) {
+ if (PRINT_ALL_THREADS ||
+ e.thr.equals(Thread.currentThread()) ||
+ e.thr.getName().equals("JVMTI_THREAD-Test1919")) {
+ System.out.println(e.name + ": " + e.thr.getName());
+ }
+ }
+ }
+
+ static class Event {
+ public final String name;
+ public final Thread thr;
+ public Event(String name, Thread thr) {
+ this.name = name;
+ this.thr = thr;
+ }
+ }
+
+ public static Event[] getEvents() {
+ String[] ns = getEventNames();
+ Thread[] ts = getEventThreads();
+ Event[] es = new Event[Math.min(ns.length, ts.length)];
+ for (int i = 0; i < es.length; i++) {
+ es[i] = new Event(ns[i], ts[i]);
+ }
+ return es;
+ }
+
+ public static native String[] getEventNames();
+ public static native Thread[] getEventThreads();
+}
diff --git a/test/1919-vminit-thread-start-timing/vminit.cc b/test/1919-vminit-thread-start-timing/vminit.cc
new file mode 100644
index 0000000..109c61f
--- /dev/null
+++ b/test/1919-vminit-thread-start-timing/vminit.cc
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "1919-vminit-thread-start-timing/vminit.h"
+
+#include <mutex>
+#include <thread>
+#include <vector>
+
+#include <jni.h>
+#include <stdio.h>
+#include <string.h>
+#include "android-base/macros.h"
+#include "jvmti.h"
+
+// Test infrastructure
+#include "scoped_local_ref.h"
+#include "jvmti_helper.h"
+#include "jni_helper.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test1919VMInitThreadStart {
+
+struct EventData {
+ std::string event;
+ jobject data;
+};
+
+struct EventList {
+ jrawMonitorID events_mutex;
+ std::vector<EventData> events;
+};
+
+
+static void EnableEvent(jvmtiEnv* env, jvmtiEvent evt) {
+ jvmtiError error = env->SetEventNotificationMode(JVMTI_ENABLE, evt, nullptr);
+ if (error != JVMTI_ERROR_NONE) {
+ printf("Failed to enable event");
+ }
+}
+
+static void JNICALL ThreadStartCallback(jvmtiEnv *jvmti, JNIEnv* env, jthread thread) {
+ EventList* list = nullptr;
+ CheckJvmtiError(jvmti, jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&list)));
+ CheckJvmtiError(jvmti, jvmti->RawMonitorEnter(list->events_mutex));
+ list->events.push_back({ "ThreadStart", env->NewGlobalRef(thread) });
+ CheckJvmtiError(jvmti, jvmti->RawMonitorExit(list->events_mutex));
+}
+
+static void JNICALL Test1919AgentThread(jvmtiEnv* jvmti,
+ JNIEnv* env,
+ void* arg ATTRIBUTE_UNUSED) {
+ EventList* list = nullptr;
+ CheckJvmtiError(jvmti, jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&list)));
+ CheckJvmtiError(jvmti, jvmti->RawMonitorEnter(list->events_mutex));
+ jthread cur;
+ CheckJvmtiError(jvmti, jvmti->GetCurrentThread(&cur));
+ list->events.push_back({ "Test1919AgentThread", env->NewGlobalRef(cur) });
+ env->DeleteLocalRef(cur);
+ // Wake up VMInit
+ CheckJvmtiError(jvmti, jvmti->RawMonitorNotify(list->events_mutex));
+ CheckJvmtiError(jvmti, jvmti->RawMonitorExit(list->events_mutex));
+}
+
+static void CreateAgentThread(jvmtiEnv* jvmti, JNIEnv* env) {
+ // Create a Thread object.
+ ScopedLocalRef<jobject> thread_name(env, env->NewStringUTF("JVMTI_THREAD-Test1919"));
+ CHECK(thread_name.get() != nullptr);
+
+ ScopedLocalRef<jclass> thread_klass(env, env->FindClass("java/lang/Thread"));
+ CHECK(thread_klass.get() != nullptr);
+
+ ScopedLocalRef<jobject> thread(env, env->AllocObject(thread_klass.get()));
+ CHECK(thread.get() != nullptr);
+
+ jmethodID initID = env->GetMethodID(thread_klass.get(), "<init>", "(Ljava/lang/String;)V");
+ CHECK(initID != nullptr);
+
+ env->CallNonvirtualVoidMethod(thread.get(), thread_klass.get(), initID, thread_name.get());
+ CHECK(!env->ExceptionCheck());
+
+ // Run agent thread.
+ CheckJvmtiError(jvmti, jvmti->RunAgentThread(thread.get(),
+ Test1919AgentThread,
+ nullptr,
+ JVMTI_THREAD_NORM_PRIORITY));
+}
+
+static void JNICALL VMInitCallback(jvmtiEnv *jvmti, JNIEnv* env, jthread thread) {
+ EventList* list = nullptr;
+ CheckJvmtiError(jvmti, jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&list)));
+ CheckJvmtiError(jvmti, jvmti->RawMonitorEnter(list->events_mutex));
+ list->events.push_back({ "VMInit", env->NewGlobalRef(thread) });
+ // Create a new thread.
+ CreateAgentThread(jvmti, env);
+ // Wait for new thread to run.
+ CheckJvmtiError(jvmti, jvmti->RawMonitorWait(list->events_mutex, 0));
+ CheckJvmtiError(jvmti, jvmti->RawMonitorExit(list->events_mutex));
+}
+
+static void InstallVMEvents(jvmtiEnv* env) {
+ jvmtiEventCallbacks callbacks;
+ memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));
+ callbacks.VMInit = VMInitCallback;
+ callbacks.ThreadStart = ThreadStartCallback;
+ jvmtiError ret = env->SetEventCallbacks(&callbacks, sizeof(callbacks));
+ if (ret != JVMTI_ERROR_NONE) {
+ printf("Failed to install callbacks");
+ }
+
+ EnableEvent(env, JVMTI_EVENT_VM_INIT);
+ EnableEvent(env, JVMTI_EVENT_THREAD_START);
+}
+
+static void InstallEventList(jvmtiEnv* env) {
+ EventList* list = nullptr;
+ CheckJvmtiError(env, env->Allocate(sizeof(EventList), reinterpret_cast<unsigned char**>(&list)));
+ memset(list, 0, sizeof(EventList));
+ CheckJvmtiError(env, env->CreateRawMonitor("Test1919 Monitor", &list->events_mutex));
+ CheckJvmtiError(env, env->SetEnvironmentLocalStorage(list));
+}
+
+jint OnLoad(JavaVM* vm,
+ char* options ATTRIBUTE_UNUSED,
+ void* reserved ATTRIBUTE_UNUSED) {
+ if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0) != 0) {
+ printf("Unable to get jvmti env!\n");
+ return 1;
+ }
+ InstallVMEvents(jvmti_env);
+ InstallEventList(jvmti_env);
+ return 0;
+}
+
+extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test1919_getEventNames(JNIEnv* env, jclass) {
+ EventList* list = nullptr;
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->GetEnvironmentLocalStorage(
+ reinterpret_cast<void**>(&list)))) {
+ return nullptr;
+ }
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorEnter(list->events_mutex))) {
+ return nullptr;
+ }
+ jobjectArray ret = CreateObjectArray(env, list->events.size(), "java/lang/String",
+ [&](jint i) {
+ return env->NewStringUTF(list->events[i].event.c_str());
+ });
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorExit(list->events_mutex))) {
+ return nullptr;
+ }
+ return ret;
+}
+
+extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test1919_getEventThreads(JNIEnv* env, jclass) {
+ EventList* list = nullptr;
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->GetEnvironmentLocalStorage(
+ reinterpret_cast<void**>(&list)))) {
+ return nullptr;
+ }
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorEnter(list->events_mutex))) {
+ return nullptr;
+ }
+ jobjectArray ret = CreateObjectArray(env, list->events.size(), "java/lang/Thread",
+ [&](jint i) {
+ return env->NewLocalRef(list->events[i].data);
+ });
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorExit(list->events_mutex))) {
+ return nullptr;
+ }
+ return ret;
+}
+
+} // namespace Test1919VMInitThreadStart
+} // namespace art
diff --git a/test/1919-vminit-thread-start-timing/vminit.h b/test/1919-vminit-thread-start-timing/vminit.h
new file mode 100644
index 0000000..c4a5ea8
--- /dev/null
+++ b/test/1919-vminit-thread-start-timing/vminit.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_TEST_1919_VMINIT_THREAD_START_TIMING_VMINIT_H_
+#define ART_TEST_1919_VMINIT_THREAD_START_TIMING_VMINIT_H_
+
+#include <jni.h>
+
+namespace art {
+namespace Test1919VMInitThreadStart {
+
+jint OnLoad(JavaVM* vm, char* options, void* reserved);
+
+} // namespace Test1919VMInitThreadStart
+} // namespace art
+
+#endif // ART_TEST_1919_VMINIT_THREAD_START_TIMING_VMINIT_H_
diff --git a/test/Android.bp b/test/Android.bp
index fab664a..9b9ab10 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -295,6 +295,7 @@
"1908-suspend-native-resume-self/native_suspend_resume.cc",
"1909-per-agent-tls/agent_tls.cc",
"1914-get-local-instance/local_instance.cc",
+ "1919-vminit-thread-start-timing/vminit.cc",
],
shared_libs: [
"libbase",
diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc
index 1d13c62..d85f33a 100644
--- a/test/ti-agent/common_load.cc
+++ b/test/ti-agent/common_load.cc
@@ -28,6 +28,7 @@
#include "909-attach-agent/attach.h"
#include "936-search-onload/search_onload.h"
#include "983-source-transform-verify/source_transform.h"
+#include "1919-vminit-thread-start-timing/vminit.h"
namespace art {
@@ -82,6 +83,7 @@
{ "941-recursive-obsolete-jit", common_redefine::OnLoad, nullptr },
{ "943-private-recursive-jit", common_redefine::OnLoad, nullptr },
{ "983-source-transform-verify", Test983SourceTransformVerify::OnLoad, nullptr },
+ { "1919-vminit-thread-start-timing", Test1919VMInitThreadStart::OnLoad, nullptr },
};
static AgentLib* FindAgent(char* name) {