summaryrefslogtreecommitdiffstats
path: root/runtime
diff options
context:
space:
mode:
authorAndreas Gampe <agampe@google.com>2017-01-17 21:40:35 -0800
committerAndreas Gampe <agampe@google.com>2017-01-18 19:48:37 +0000
commit13093d455b8266338fd713b04261c58e9dc2b164 (patch)
treeb4c5e54ce8a74af2f8929df089133967ea7f7274 /runtime
parent3a5e34b65727d2c9e7e6ebe45c337dae0d0398a4 (diff)
downloadart-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.bp2
-rw-r--r--runtime/base/mutex.cc6
-rw-r--r--runtime/base/mutex.h6
-rw-r--r--runtime/debugger.cc10
-rw-r--r--runtime/debugger.h21
-rw-r--r--runtime/runtime.cc1
-rw-r--r--runtime/runtime.h8
-rw-r--r--runtime/runtime_callbacks.cc48
-rw-r--r--runtime/runtime_callbacks.h49
-rw-r--r--runtime/runtime_callbacks_test.cc201
-rw-r--r--runtime/thread.cc15
-rw-r--r--runtime/thread.h8
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);