diff options
author | Andreas Gampe <agampe@google.com> | 2017-01-17 21:40:35 -0800 |
---|---|---|
committer | Andreas Gampe <agampe@google.com> | 2017-01-18 19:48:37 +0000 |
commit | 13093d455b8266338fd713b04261c58e9dc2b164 (patch) | |
tree | b4c5e54ce8a74af2f8929df089133967ea7f7274 /runtime | |
parent | 3a5e34b65727d2c9e7e6ebe45c337dae0d0398a4 (diff) | |
download | art-13093d455b8266338fd713b04261c58e9dc2b164.tar.gz art-13093d455b8266338fd713b04261c58e9dc2b164.tar.bz2 art-13093d455b8266338fd713b04261c58e9dc2b164.zip |
ART: Start RuntimeCallbacks
Add a central RuntimeCallbacks structure to handle certain interesting
runtime events.
In a first iteration, add ThreadLifecycleCallback with ThreadStart and
ThreadStop. Move Dbg over to ThreadLifecycleCallback.
Add a test.
Bug: 31684920
Test: m test-art-host-gtest-runtime_callbacks_test
Test: art/tools/run-jdwp-tests.sh --mode=host
Change-Id: Ie0f77739a563207bfb4f04374e72dc6935c40b4f
Diffstat (limited to 'runtime')
-rw-r--r-- | runtime/Android.bp | 2 | ||||
-rw-r--r-- | runtime/base/mutex.cc | 6 | ||||
-rw-r--r-- | runtime/base/mutex.h | 6 | ||||
-rw-r--r-- | runtime/debugger.cc | 10 | ||||
-rw-r--r-- | runtime/debugger.h | 21 | ||||
-rw-r--r-- | runtime/runtime.cc | 1 | ||||
-rw-r--r-- | runtime/runtime.h | 8 | ||||
-rw-r--r-- | runtime/runtime_callbacks.cc | 48 | ||||
-rw-r--r-- | runtime/runtime_callbacks.h | 49 | ||||
-rw-r--r-- | runtime/runtime_callbacks_test.cc | 201 | ||||
-rw-r--r-- | runtime/thread.cc | 15 | ||||
-rw-r--r-- | runtime/thread.h | 8 |
12 files changed, 367 insertions, 8 deletions
diff --git a/runtime/Android.bp b/runtime/Android.bp index 86019bf71c..dd91249e25 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -184,6 +184,7 @@ cc_defaults { "reference_table.cc", "reflection.cc", "runtime.cc", + "runtime_callbacks.cc", "runtime_options.cc", "signal_catcher.cc", "stack.cc", @@ -563,6 +564,7 @@ art_cc_test { "parsed_options_test.cc", "prebuilt_tools_test.cc", "reference_table_test.cc", + "runtime_callbacks_test.cc", "thread_pool_test.cc", "transaction_test.cc", "type_lookup_table_test.cc", diff --git a/runtime/base/mutex.cc b/runtime/base/mutex.cc index 9116097604..edb58c4eb7 100644 --- a/runtime/base/mutex.cc +++ b/runtime/base/mutex.cc @@ -61,6 +61,7 @@ Mutex* Locks::reference_queue_finalizer_references_lock_ = nullptr; Mutex* Locks::reference_queue_phantom_references_lock_ = nullptr; Mutex* Locks::reference_queue_soft_references_lock_ = nullptr; Mutex* Locks::reference_queue_weak_references_lock_ = nullptr; +ReaderWriterMutex* Locks::runtime_callbacks_lock_ = nullptr; Mutex* Locks::runtime_shutdown_lock_ = nullptr; Mutex* Locks::cha_lock_ = nullptr; Mutex* Locks::thread_list_lock_ = nullptr; @@ -967,6 +968,7 @@ void Locks::Init() { DCHECK(trace_lock_ != nullptr); DCHECK(unexpected_signal_lock_ != nullptr); DCHECK(dex_lock_ != nullptr); + DCHECK(runtime_callbacks_lock_ != nullptr); } else { // Create global locks in level order from highest lock level to lowest. LockLevel current_lock_level = kInstrumentEntrypointsLock; @@ -998,6 +1000,10 @@ void Locks::Init() { DCHECK(runtime_shutdown_lock_ == nullptr); runtime_shutdown_lock_ = new Mutex("runtime shutdown lock", current_lock_level); + UPDATE_CURRENT_LOCK_LEVEL(kRuntimeCallbacksLock); + DCHECK(runtime_callbacks_lock_ == nullptr); + runtime_callbacks_lock_ = new ReaderWriterMutex("runtime callbacks lock", current_lock_level); + UPDATE_CURRENT_LOCK_LEVEL(kProfilerLock); DCHECK(profiler_lock_ == nullptr); profiler_lock_ = new Mutex("profiler lock", current_lock_level); diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h index 2adeb8cc97..e44bdb89cc 100644 --- a/runtime/base/mutex.h +++ b/runtime/base/mutex.h @@ -111,6 +111,7 @@ enum LockLevel { kJdwpEventListLock, kJdwpAttachLock, kJdwpStartLock, + kRuntimeCallbacksLock, kRuntimeShutdownLock, kTraceLock, kHeapBitmapLock, @@ -615,8 +616,11 @@ class Locks { // Guards shutdown of the runtime. static Mutex* runtime_shutdown_lock_ ACQUIRED_AFTER(heap_bitmap_lock_); + // Guards accesses to runtime callback lists. + static ReaderWriterMutex* runtime_callbacks_lock_ ACQUIRED_AFTER(runtime_shutdown_lock_); + // Guards background profiler global state. - static Mutex* profiler_lock_ ACQUIRED_AFTER(runtime_shutdown_lock_); + static Mutex* profiler_lock_ ACQUIRED_AFTER(runtime_callbacks_lock_); // Guards trace (ie traceview) requests. static Mutex* trace_lock_ ACQUIRED_AFTER(profiler_lock_); diff --git a/runtime/debugger.cc b/runtime/debugger.cc index df4413d52c..c97c4e4f5b 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -320,6 +320,8 @@ size_t Dbg::field_write_event_ref_count_ = 0; size_t Dbg::exception_catch_event_ref_count_ = 0; uint32_t Dbg::instrumentation_events_ = 0; +Dbg::DbgThreadLifecycleCallback Dbg::thread_lifecycle_callback_; + // Breakpoints. static std::vector<Breakpoint> gBreakpoints GUARDED_BY(Locks::breakpoint_lock_); @@ -5137,4 +5139,12 @@ void Dbg::VisitRoots(RootVisitor* visitor) { } } +void Dbg::DbgThreadLifecycleCallback::ThreadStart(Thread* self) { + Dbg::PostThreadStart(self); +} + +void Dbg::DbgThreadLifecycleCallback::ThreadDeath(Thread* self) { + Dbg::PostThreadDeath(self); +} + } // namespace art diff --git a/runtime/debugger.h b/runtime/debugger.h index 3b4a5e16b0..01359907d9 100644 --- a/runtime/debugger.h +++ b/runtime/debugger.h @@ -502,10 +502,6 @@ class Dbg { REQUIRES_SHARED(Locks::mutator_lock_); static void PostException(mirror::Throwable* exception) REQUIRES_SHARED(Locks::mutator_lock_); - static void PostThreadStart(Thread* t) - REQUIRES_SHARED(Locks::mutator_lock_); - static void PostThreadDeath(Thread* t) - REQUIRES_SHARED(Locks::mutator_lock_); static void PostClassPrepare(mirror::Class* c) REQUIRES_SHARED(Locks::mutator_lock_); @@ -707,6 +703,10 @@ class Dbg { return instrumentation_events_; } + static ThreadLifecycleCallback* GetThreadLifecycleCallback() { + return &thread_lifecycle_callback_; + } + private: static void ExecuteMethodWithoutPendingException(ScopedObjectAccess& soa, DebugInvokeReq* pReq) REQUIRES_SHARED(Locks::mutator_lock_); @@ -725,6 +725,11 @@ class Dbg { REQUIRES(!Locks::thread_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_); static void DdmBroadcast(bool connect) REQUIRES_SHARED(Locks::mutator_lock_); + + static void PostThreadStart(Thread* t) + REQUIRES_SHARED(Locks::mutator_lock_); + static void PostThreadDeath(Thread* t) + REQUIRES_SHARED(Locks::mutator_lock_); static void PostThreadStartOrStop(Thread*, uint32_t) REQUIRES_SHARED(Locks::mutator_lock_); @@ -789,6 +794,14 @@ class Dbg { static size_t exception_catch_event_ref_count_ GUARDED_BY(Locks::deoptimization_lock_); static uint32_t instrumentation_events_ GUARDED_BY(Locks::mutator_lock_); + class DbgThreadLifecycleCallback : public ThreadLifecycleCallback { + public: + void ThreadStart(Thread* self) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_); + void ThreadDeath(Thread* self) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_); + }; + + static DbgThreadLifecycleCallback thread_lifecycle_callback_; + DISALLOW_COPY_AND_ASSIGN(Dbg); }; diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 55e1852c0c..6ef5f26ce5 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -1100,6 +1100,7 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { if (runtime_options.Exists(Opt::JdwpOptions)) { Dbg::ConfigureJdwp(runtime_options.GetOrDefault(Opt::JdwpOptions)); } + callbacks_.AddThreadLifecycleCallback(Dbg::GetThreadLifecycleCallback()); jit_options_.reset(jit::JitOptions::CreateFromRuntimeArguments(runtime_options)); if (IsAotCompiler()) { diff --git a/runtime/runtime.h b/runtime/runtime.h index a87e1c136b..0c5de4eedf 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -28,6 +28,7 @@ #include "arch/instruction_set.h" #include "base/macros.h" +#include "base/mutex.h" #include "dex_file_types.h" #include "experimental_flags.h" #include "gc_root.h" @@ -39,6 +40,7 @@ #include "offsets.h" #include "process_state.h" #include "quick/quick_method_frame_info.h" +#include "runtime_callbacks.h" #include "runtime_stats.h" #include "safe_map.h" @@ -660,6 +662,10 @@ class Runtime { void AttachAgent(const std::string& agent_arg); + RuntimeCallbacks& GetRuntimeCallbacks() { + return callbacks_; + } + private: static void InitPlatformSignalHandlers(); @@ -917,6 +923,8 @@ class Runtime { ClassHierarchyAnalysis* cha_; + RuntimeCallbacks callbacks_; + DISALLOW_COPY_AND_ASSIGN(Runtime); }; std::ostream& operator<<(std::ostream& os, const Runtime::CalleeSaveType& rhs); diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc new file mode 100644 index 0000000000..a523ddfa44 --- /dev/null +++ b/runtime/runtime_callbacks.cc @@ -0,0 +1,48 @@ +/* + * 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. + */ + +#include "runtime_callbacks.h" + +#include <algorithm> + +#include "thread.h" + +namespace art { + +void RuntimeCallbacks::AddThreadLifecycleCallback(ThreadLifecycleCallback* cb) { + thread_callbacks_.push_back(cb); +} + +void RuntimeCallbacks::RemoveThreadLifecycleCallback(ThreadLifecycleCallback* cb) { + auto it = std::find(thread_callbacks_.begin(), thread_callbacks_.end(), cb); + if (it != thread_callbacks_.end()) { + thread_callbacks_.erase(it); + } +} + +void RuntimeCallbacks::ThreadStart(Thread* self) { + for (ThreadLifecycleCallback* cb : thread_callbacks_) { + cb->ThreadStart(self); + } +} + +void RuntimeCallbacks::ThreadDeath(Thread* self) { + for (ThreadLifecycleCallback* cb : thread_callbacks_) { + cb->ThreadDeath(self); + } +} + +} // namespace art diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h new file mode 100644 index 0000000000..39eef3bb20 --- /dev/null +++ b/runtime/runtime_callbacks.h @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#ifndef ART_RUNTIME_RUNTIME_CALLBACKS_H_ +#define ART_RUNTIME_RUNTIME_CALLBACKS_H_ + +#include <vector> + +#include "base/macros.h" +#include "base/mutex.h" + +namespace art { + +class Thread; +class ThreadLifecycleCallback; + +class RuntimeCallbacks { + public: + void AddThreadLifecycleCallback(ThreadLifecycleCallback* cb) + REQUIRES(Locks::runtime_callbacks_lock_); + void RemoveThreadLifecycleCallback(ThreadLifecycleCallback* cb) + REQUIRES(Locks::runtime_callbacks_lock_); + + void ThreadStart(Thread* self) + REQUIRES_SHARED(Locks::mutator_lock_, Locks::runtime_callbacks_lock_); + void ThreadDeath(Thread* self) + REQUIRES_SHARED(Locks::mutator_lock_, Locks::runtime_callbacks_lock_); + + private: + std::vector<ThreadLifecycleCallback*> thread_callbacks_ + GUARDED_BY(Locks::runtime_callbacks_lock_); +}; + +} // namespace art + +#endif // ART_RUNTIME_RUNTIME_CALLBACKS_H_ diff --git a/runtime/runtime_callbacks_test.cc b/runtime/runtime_callbacks_test.cc new file mode 100644 index 0000000000..00a4062065 --- /dev/null +++ b/runtime/runtime_callbacks_test.cc @@ -0,0 +1,201 @@ +/* + * 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. + */ + +#include "runtime_callbacks.h" + +#include "jni.h" +#include <memory> +#include <string> + +#include "art_method-inl.h" +#include "base/mutex.h" +#include "mirror/class-inl.h" +#include "common_runtime_test.h" +#include "mem_map.h" +#include "obj_ptr.h" +#include "runtime.h" +#include "ScopedLocalRef.h" +#include "thread-inl.h" +#include "well_known_classes.h" + +namespace art { + +class RuntimeCallbacksTest : public CommonRuntimeTest { + protected: + void SetUp() OVERRIDE { + CommonRuntimeTest::SetUp(); + + WriterMutexLock mu(Thread::Current(), *Locks::runtime_callbacks_lock_); + AddListener(); + } + + void TearDown() OVERRIDE { + { + WriterMutexLock mu(Thread::Current(), *Locks::runtime_callbacks_lock_); + RemoveListener(); + } + + CommonRuntimeTest::TearDown(); + } + + virtual void AddListener() REQUIRES(Locks::runtime_callbacks_lock_) = 0; + virtual void RemoveListener() REQUIRES(Locks::runtime_callbacks_lock_) = 0; + + void MakeExecutable(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) { + CHECK(klass != nullptr); + PointerSize pointer_size = class_linker_->GetImagePointerSize(); + for (auto& m : klass->GetMethods(pointer_size)) { + if (!m.IsAbstract()) { + class_linker_->SetEntryPointsToInterpreter(&m); + } + } + } +}; + +class ThreadLifecycleCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest { + public: + static void* PthreadsCallback(void* arg ATTRIBUTE_UNUSED) { + // Attach. + Runtime* runtime = Runtime::Current(); + CHECK(runtime->AttachCurrentThread("ThreadLifecycle test thread", true, nullptr, false)); + + // Detach. + runtime->DetachCurrentThread(); + + // Die... + return nullptr; + } + + protected: + void AddListener() OVERRIDE REQUIRES(Locks::runtime_callbacks_lock_) { + Runtime::Current()->GetRuntimeCallbacks().AddThreadLifecycleCallback(&cb_); + } + void RemoveListener() OVERRIDE REQUIRES(Locks::runtime_callbacks_lock_) { + Runtime::Current()->GetRuntimeCallbacks().RemoveThreadLifecycleCallback(&cb_); + } + + enum CallbackState { + kBase, + kStarted, + kDied, + kWrongStart, + kWrongDeath, + }; + + struct Callback : public ThreadLifecycleCallback { + void ThreadStart(Thread* self) OVERRIDE { + if (state == CallbackState::kBase) { + state = CallbackState::kStarted; + stored_self = self; + } else { + state = CallbackState::kWrongStart; + } + } + + void ThreadDeath(Thread* self) OVERRIDE { + if (state == CallbackState::kStarted && self == stored_self) { + state = CallbackState::kDied; + } else { + state = CallbackState::kWrongDeath; + } + } + + Thread* stored_self; + CallbackState state = CallbackState::kBase; + }; + + Callback cb_; +}; + + +TEST_F(ThreadLifecycleCallbackRuntimeCallbacksTest, ThreadLifecycleCallbackJava) { + Thread* self = Thread::Current(); + + self->TransitionFromSuspendedToRunnable(); + bool started = runtime_->Start(); + ASSERT_TRUE(started); + + cb_.state = CallbackState::kBase; // Ignore main thread attach. + + { + ScopedObjectAccess soa(self); + MakeExecutable(soa.Decode<mirror::Class>(WellKnownClasses::java_lang_Thread)); + } + + JNIEnv* env = self->GetJniEnv(); + + ScopedLocalRef<jobject> thread_name(env, + env->NewStringUTF("ThreadLifecycleCallback test thread")); + ASSERT_TRUE(thread_name.get() != nullptr); + + ScopedLocalRef<jobject> thread(env, env->AllocObject(WellKnownClasses::java_lang_Thread)); + ASSERT_TRUE(thread.get() != nullptr); + + env->CallNonvirtualVoidMethod(thread.get(), + WellKnownClasses::java_lang_Thread, + WellKnownClasses::java_lang_Thread_init, + runtime_->GetMainThreadGroup(), + thread_name.get(), + kMinThreadPriority, + JNI_FALSE); + ASSERT_FALSE(env->ExceptionCheck()); + + jmethodID start_id = env->GetMethodID(WellKnownClasses::java_lang_Thread, "start", "()V"); + ASSERT_TRUE(start_id != nullptr); + + env->CallVoidMethod(thread.get(), start_id); + ASSERT_FALSE(env->ExceptionCheck()); + + jmethodID join_id = env->GetMethodID(WellKnownClasses::java_lang_Thread, "join", "()V"); + ASSERT_TRUE(join_id != nullptr); + + env->CallVoidMethod(thread.get(), join_id); + ASSERT_FALSE(env->ExceptionCheck()); + + EXPECT_TRUE(cb_.state == CallbackState::kDied) << static_cast<int>(cb_.state); +} + +TEST_F(ThreadLifecycleCallbackRuntimeCallbacksTest, ThreadLifecycleCallbackAttach) { + std::string error_msg; + std::unique_ptr<MemMap> stack(MemMap::MapAnonymous("ThreadLifecycleCallback Thread", + nullptr, + 128 * kPageSize, // Just some small stack. + PROT_READ | PROT_WRITE, + false, + false, + &error_msg)); + ASSERT_FALSE(stack == nullptr) << error_msg; + + const char* reason = "ThreadLifecycleCallback test thread"; + pthread_attr_t attr; + CHECK_PTHREAD_CALL(pthread_attr_init, (&attr), reason); + CHECK_PTHREAD_CALL(pthread_attr_setstack, (&attr, stack->Begin(), stack->Size()), reason); + pthread_t pthread; + CHECK_PTHREAD_CALL(pthread_create, + (&pthread, + &attr, + &ThreadLifecycleCallbackRuntimeCallbacksTest::PthreadsCallback, + this), + reason); + CHECK_PTHREAD_CALL(pthread_attr_destroy, (&attr), reason); + + CHECK_PTHREAD_CALL(pthread_join, (pthread, nullptr), "ThreadLifecycleCallback test shutdown"); + + // Detach is not a ThreadDeath event, so we expect to be in state Started. + EXPECT_TRUE(cb_.state == CallbackState::kStarted) << static_cast<int>(cb_.state); +} + +} // namespace art diff --git a/runtime/thread.cc b/runtime/thread.cc index 40b6d73d94..bf69e1010d 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -431,7 +431,10 @@ void* Thread::CreateCallback(void* arg) { ArtField* priorityField = jni::DecodeArtField(WellKnownClasses::java_lang_Thread_priority); self->SetNativePriority(priorityField->GetInt(self->tlsPtr_.opeer)); - Dbg::PostThreadStart(self); + { + ReaderMutexLock mu(self, *Locks::runtime_callbacks_lock_); + runtime->GetRuntimeCallbacks().ThreadStart(self); + } // Invoke the 'run' method of our java.lang.Thread. ObjPtr<mirror::Object> receiver = self->tlsPtr_.opeer; @@ -793,7 +796,8 @@ Thread* Thread::Attach(const char* thread_name, bool as_daemon, jobject thread_g { ScopedObjectAccess soa(self); - Dbg::PostThreadStart(self); + ReaderMutexLock mu(self, *Locks::runtime_callbacks_lock_); + runtime->GetRuntimeCallbacks().ThreadStart(self); } return self; @@ -1929,7 +1933,12 @@ void Thread::Destroy() { jni::DecodeArtField(WellKnownClasses::java_lang_Thread_nativePeer) ->SetLong<false>(tlsPtr_.opeer, 0); } - Dbg::PostThreadDeath(self); + Runtime* runtime = Runtime::Current(); + if (runtime != nullptr) { + ReaderMutexLock mu(self, *Locks::runtime_callbacks_lock_); + runtime->GetRuntimeCallbacks().ThreadDeath(self); + } + // Thread.join() is implemented as an Object.wait() on the Thread.lock object. Signal anyone // who is waiting. diff --git a/runtime/thread.h b/runtime/thread.h index 2b451bcaee..166ed217d0 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -1704,6 +1704,14 @@ class ScopedTransitioningToRunnable : public ValueObject { Thread* const self_; }; +class ThreadLifecycleCallback { + public: + virtual ~ThreadLifecycleCallback() {} + + virtual void ThreadStart(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) = 0; + virtual void ThreadDeath(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) = 0; +}; + std::ostream& operator<<(std::ostream& os, const Thread& thread); std::ostream& operator<<(std::ostream& os, const StackedShadowFrameType& thread); |