You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
534 lines
17 KiB
534 lines
17 KiB
4 months ago
|
/*
|
||
|
* 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 <signal.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
#include <initializer_list>
|
||
|
#include <memory>
|
||
|
#include <mutex>
|
||
|
#include <string>
|
||
|
|
||
|
#include "jni.h"
|
||
|
|
||
|
#include "art_method-inl.h"
|
||
|
#include "base/mem_map.h"
|
||
|
#include "base/mutex.h"
|
||
|
#include "class_linker.h"
|
||
|
#include "common_runtime_test.h"
|
||
|
#include "dex/class_reference.h"
|
||
|
#include "handle.h"
|
||
|
#include "handle_scope-inl.h"
|
||
|
#include "mirror/class-inl.h"
|
||
|
#include "mirror/class_loader.h"
|
||
|
#include "monitor-inl.h"
|
||
|
#include "nativehelper/scoped_local_ref.h"
|
||
|
#include "obj_ptr-inl.h"
|
||
|
#include "runtime.h"
|
||
|
#include "scoped_thread_state_change-inl.h"
|
||
|
#include "thread-inl.h"
|
||
|
#include "thread_list.h"
|
||
|
#include "well_known_classes.h"
|
||
|
|
||
|
namespace art {
|
||
|
|
||
|
class RuntimeCallbacksTest : public CommonRuntimeTest {
|
||
|
protected:
|
||
|
void SetUp() override {
|
||
|
CommonRuntimeTest::SetUp();
|
||
|
|
||
|
Thread* self = Thread::Current();
|
||
|
ScopedObjectAccess soa(self);
|
||
|
ScopedThreadSuspension sts(self, kWaitingForDebuggerToAttach);
|
||
|
ScopedSuspendAll ssa("RuntimeCallbacksTest SetUp");
|
||
|
AddListener();
|
||
|
}
|
||
|
|
||
|
void TearDown() override {
|
||
|
{
|
||
|
Thread* self = Thread::Current();
|
||
|
ScopedObjectAccess soa(self);
|
||
|
ScopedThreadSuspension sts(self, kWaitingForDebuggerToAttach);
|
||
|
ScopedSuspendAll ssa("RuntimeCallbacksTest TearDown");
|
||
|
RemoveListener();
|
||
|
}
|
||
|
|
||
|
CommonRuntimeTest::TearDown();
|
||
|
}
|
||
|
|
||
|
virtual void AddListener() REQUIRES(Locks::mutator_lock_) = 0;
|
||
|
virtual void RemoveListener() REQUIRES(Locks::mutator_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::mutator_lock_) {
|
||
|
Runtime::Current()->GetRuntimeCallbacks()->AddThreadLifecycleCallback(&cb_);
|
||
|
}
|
||
|
void RemoveListener() override REQUIRES(Locks::mutator_lock_) {
|
||
|
Runtime::Current()->GetRuntimeCallbacks()->RemoveThreadLifecycleCallback(&cb_);
|
||
|
}
|
||
|
|
||
|
enum CallbackState {
|
||
|
kBase,
|
||
|
kStarted,
|
||
|
kDied,
|
||
|
kWrongStart,
|
||
|
kWrongDeath,
|
||
|
};
|
||
|
|
||
|
struct Callback : public ThreadLifecycleCallback {
|
||
|
void ThreadStart(Thread* self) override {
|
||
|
{
|
||
|
ScopedObjectAccess soa(self);
|
||
|
LOG(DEBUG) << "ThreadStart callback for thread: " << self->GetThreadName();
|
||
|
}
|
||
|
if (state == CallbackState::kBase) {
|
||
|
state = CallbackState::kStarted;
|
||
|
stored_self = self;
|
||
|
} else {
|
||
|
state = CallbackState::kWrongStart;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ThreadDeath(Thread* self) override {
|
||
|
{
|
||
|
ScopedObjectAccess soa(self);
|
||
|
LOG(DEBUG) << "ThreadDeath callback for thread: " << self->GetThreadName();
|
||
|
}
|
||
|
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);
|
||
|
// Make sure the workers are done starting so we don't get callbacks for them.
|
||
|
runtime_->WaitForThreadPoolWorkersToStart();
|
||
|
|
||
|
// The metrics reporting thread will sometimes be slow to start. Synchronously requesting a
|
||
|
// metrics report forces us to wait until the thread has started.
|
||
|
runtime_->RequestMetricsReport(/*synchronous=*/true);
|
||
|
|
||
|
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_EQ(cb_.state, CallbackState::kDied);
|
||
|
}
|
||
|
|
||
|
TEST_F(ThreadLifecycleCallbackRuntimeCallbacksTest, ThreadLifecycleCallbackAttach) {
|
||
|
std::string error_msg;
|
||
|
MemMap stack = MemMap::MapAnonymous("ThreadLifecycleCallback Thread",
|
||
|
128 * kPageSize, // Just some small stack.
|
||
|
PROT_READ | PROT_WRITE,
|
||
|
/*low_4gb=*/ false,
|
||
|
&error_msg);
|
||
|
ASSERT_TRUE(stack.IsValid()) << 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);
|
||
|
}
|
||
|
|
||
|
class ClassLoadCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest {
|
||
|
protected:
|
||
|
void AddListener() override REQUIRES(Locks::mutator_lock_) {
|
||
|
Runtime::Current()->GetRuntimeCallbacks()->AddClassLoadCallback(&cb_);
|
||
|
}
|
||
|
void RemoveListener() override REQUIRES(Locks::mutator_lock_) {
|
||
|
Runtime::Current()->GetRuntimeCallbacks()->RemoveClassLoadCallback(&cb_);
|
||
|
}
|
||
|
|
||
|
bool Expect(std::initializer_list<const char*> list) {
|
||
|
if (cb_.data.size() != list.size()) {
|
||
|
PrintError(list);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (!std::equal(cb_.data.begin(), cb_.data.end(), list.begin())) {
|
||
|
PrintError(list);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void PrintError(std::initializer_list<const char*> list) {
|
||
|
LOG(ERROR) << "Expected:";
|
||
|
for (const char* expected : list) {
|
||
|
LOG(ERROR) << " " << expected;
|
||
|
}
|
||
|
LOG(ERROR) << "Found:";
|
||
|
for (const auto& s : cb_.data) {
|
||
|
LOG(ERROR) << " " << s;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
struct Callback : public ClassLoadCallback {
|
||
|
void ClassPreDefine(const char* descriptor,
|
||
|
Handle<mirror::Class> klass ATTRIBUTE_UNUSED,
|
||
|
Handle<mirror::ClassLoader> class_loader ATTRIBUTE_UNUSED,
|
||
|
const DexFile& initial_dex_file,
|
||
|
const dex::ClassDef& initial_class_def ATTRIBUTE_UNUSED,
|
||
|
/*out*/DexFile const** final_dex_file ATTRIBUTE_UNUSED,
|
||
|
/*out*/dex::ClassDef const** final_class_def ATTRIBUTE_UNUSED) override
|
||
|
REQUIRES_SHARED(Locks::mutator_lock_) {
|
||
|
const std::string& location = initial_dex_file.GetLocation();
|
||
|
std::string event =
|
||
|
std::string("PreDefine:") + descriptor + " <" +
|
||
|
location.substr(location.rfind('/') + 1, location.size()) + ">";
|
||
|
data.push_back(event);
|
||
|
}
|
||
|
|
||
|
void ClassLoad(Handle<mirror::Class> klass) override REQUIRES_SHARED(Locks::mutator_lock_) {
|
||
|
std::string tmp;
|
||
|
std::string event = std::string("Load:") + klass->GetDescriptor(&tmp);
|
||
|
data.push_back(event);
|
||
|
}
|
||
|
|
||
|
void ClassPrepare(Handle<mirror::Class> temp_klass,
|
||
|
Handle<mirror::Class> klass) override REQUIRES_SHARED(Locks::mutator_lock_) {
|
||
|
std::string tmp, tmp2;
|
||
|
std::string event = std::string("Prepare:") + klass->GetDescriptor(&tmp)
|
||
|
+ "[" + temp_klass->GetDescriptor(&tmp2) + "]";
|
||
|
data.push_back(event);
|
||
|
}
|
||
|
|
||
|
std::vector<std::string> data;
|
||
|
};
|
||
|
|
||
|
Callback cb_;
|
||
|
};
|
||
|
|
||
|
TEST_F(ClassLoadCallbackRuntimeCallbacksTest, ClassLoadCallback) {
|
||
|
ScopedObjectAccess soa(Thread::Current());
|
||
|
jobject jclass_loader = LoadDex("XandY");
|
||
|
VariableSizedHandleScope hs(soa.Self());
|
||
|
Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
|
||
|
soa.Decode<mirror::ClassLoader>(jclass_loader)));
|
||
|
|
||
|
const char* descriptor_y = "LY;";
|
||
|
Handle<mirror::Class> h_Y(
|
||
|
hs.NewHandle(class_linker_->FindClass(soa.Self(), descriptor_y, class_loader)));
|
||
|
ASSERT_TRUE(h_Y != nullptr);
|
||
|
|
||
|
bool expect1 = Expect({ "PreDefine:LY; <art-gtest-jars-XandY.jar>",
|
||
|
"PreDefine:LX; <art-gtest-jars-XandY.jar>",
|
||
|
"Load:LX;",
|
||
|
"Prepare:LX;[LX;]",
|
||
|
"Load:LY;",
|
||
|
"Prepare:LY;[LY;]" });
|
||
|
EXPECT_TRUE(expect1);
|
||
|
|
||
|
cb_.data.clear();
|
||
|
|
||
|
ASSERT_TRUE(class_linker_->EnsureInitialized(Thread::Current(), h_Y, true, true));
|
||
|
|
||
|
bool expect2 = Expect({ "PreDefine:LY$Z; <art-gtest-jars-XandY.jar>",
|
||
|
"Load:LY$Z;",
|
||
|
"Prepare:LY$Z;[LY$Z;]" });
|
||
|
EXPECT_TRUE(expect2);
|
||
|
}
|
||
|
|
||
|
class RuntimeSigQuitCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest {
|
||
|
protected:
|
||
|
void AddListener() override REQUIRES(Locks::mutator_lock_) {
|
||
|
Runtime::Current()->GetRuntimeCallbacks()->AddRuntimeSigQuitCallback(&cb_);
|
||
|
}
|
||
|
void RemoveListener() override REQUIRES(Locks::mutator_lock_) {
|
||
|
Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimeSigQuitCallback(&cb_);
|
||
|
}
|
||
|
|
||
|
struct Callback : public RuntimeSigQuitCallback {
|
||
|
void SigQuit() override {
|
||
|
++sigquit_count;
|
||
|
}
|
||
|
|
||
|
size_t sigquit_count = 0;
|
||
|
};
|
||
|
|
||
|
Callback cb_;
|
||
|
};
|
||
|
|
||
|
TEST_F(RuntimeSigQuitCallbackRuntimeCallbacksTest, SigQuit) {
|
||
|
// The runtime needs to be started for the signal handler.
|
||
|
Thread* self = Thread::Current();
|
||
|
|
||
|
self->TransitionFromSuspendedToRunnable();
|
||
|
bool started = runtime_->Start();
|
||
|
ASSERT_TRUE(started);
|
||
|
|
||
|
EXPECT_EQ(0u, cb_.sigquit_count);
|
||
|
|
||
|
kill(getpid(), SIGQUIT);
|
||
|
|
||
|
// Try a few times.
|
||
|
for (size_t i = 0; i != 30; ++i) {
|
||
|
if (cb_.sigquit_count == 0) {
|
||
|
sleep(1);
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
EXPECT_EQ(1u, cb_.sigquit_count);
|
||
|
}
|
||
|
|
||
|
class RuntimePhaseCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest {
|
||
|
protected:
|
||
|
void AddListener() override REQUIRES(Locks::mutator_lock_) {
|
||
|
Runtime::Current()->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&cb_);
|
||
|
}
|
||
|
void RemoveListener() override REQUIRES(Locks::mutator_lock_) {
|
||
|
Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&cb_);
|
||
|
}
|
||
|
|
||
|
void TearDown() override {
|
||
|
// Bypass RuntimeCallbacksTest::TearDown, as the runtime is already gone.
|
||
|
CommonRuntimeTest::TearDown();
|
||
|
}
|
||
|
|
||
|
struct Callback : public RuntimePhaseCallback {
|
||
|
void NextRuntimePhase(RuntimePhaseCallback::RuntimePhase p) override {
|
||
|
if (p == RuntimePhaseCallback::RuntimePhase::kInitialAgents) {
|
||
|
if (start_seen > 0 || init_seen > 0 || death_seen > 0) {
|
||
|
LOG(FATAL) << "Unexpected order";
|
||
|
}
|
||
|
++initial_agents_seen;
|
||
|
} else if (p == RuntimePhaseCallback::RuntimePhase::kStart) {
|
||
|
if (init_seen > 0 || death_seen > 0) {
|
||
|
LOG(FATAL) << "Init seen before start.";
|
||
|
}
|
||
|
++start_seen;
|
||
|
} else if (p == RuntimePhaseCallback::RuntimePhase::kInit) {
|
||
|
++init_seen;
|
||
|
} else if (p == RuntimePhaseCallback::RuntimePhase::kDeath) {
|
||
|
++death_seen;
|
||
|
} else {
|
||
|
LOG(FATAL) << "Unknown phase " << static_cast<uint32_t>(p);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
size_t initial_agents_seen = 0;
|
||
|
size_t start_seen = 0;
|
||
|
size_t init_seen = 0;
|
||
|
size_t death_seen = 0;
|
||
|
};
|
||
|
|
||
|
Callback cb_;
|
||
|
};
|
||
|
|
||
|
TEST_F(RuntimePhaseCallbackRuntimeCallbacksTest, Phases) {
|
||
|
ASSERT_EQ(0u, cb_.initial_agents_seen);
|
||
|
ASSERT_EQ(0u, cb_.start_seen);
|
||
|
ASSERT_EQ(0u, cb_.init_seen);
|
||
|
ASSERT_EQ(0u, cb_.death_seen);
|
||
|
|
||
|
// Start the runtime.
|
||
|
{
|
||
|
Thread* self = Thread::Current();
|
||
|
self->TransitionFromSuspendedToRunnable();
|
||
|
bool started = runtime_->Start();
|
||
|
ASSERT_TRUE(started);
|
||
|
}
|
||
|
|
||
|
ASSERT_EQ(0u, cb_.initial_agents_seen);
|
||
|
ASSERT_EQ(1u, cb_.start_seen);
|
||
|
ASSERT_EQ(1u, cb_.init_seen);
|
||
|
ASSERT_EQ(0u, cb_.death_seen);
|
||
|
|
||
|
// Delete the runtime.
|
||
|
runtime_.reset();
|
||
|
|
||
|
ASSERT_EQ(0u, cb_.initial_agents_seen);
|
||
|
ASSERT_EQ(1u, cb_.start_seen);
|
||
|
ASSERT_EQ(1u, cb_.init_seen);
|
||
|
ASSERT_EQ(1u, cb_.death_seen);
|
||
|
}
|
||
|
|
||
|
class MonitorWaitCallbacksTest : public RuntimeCallbacksTest {
|
||
|
protected:
|
||
|
void AddListener() override REQUIRES(Locks::mutator_lock_) {
|
||
|
Runtime::Current()->GetRuntimeCallbacks()->AddMonitorCallback(&cb_);
|
||
|
}
|
||
|
void RemoveListener() override REQUIRES(Locks::mutator_lock_) {
|
||
|
Runtime::Current()->GetRuntimeCallbacks()->RemoveMonitorCallback(&cb_);
|
||
|
}
|
||
|
|
||
|
struct Callback : public MonitorCallback {
|
||
|
bool IsInterestingObject(ObjPtr<mirror::Object> obj)
|
||
|
REQUIRES_SHARED(art::Locks::mutator_lock_) {
|
||
|
if (!obj->IsClass()) {
|
||
|
return false;
|
||
|
}
|
||
|
std::lock_guard<std::mutex> lock(ref_guard_);
|
||
|
ObjPtr<mirror::Class> k = obj->AsClass();
|
||
|
ClassReference test = { &k->GetDexFile(), k->GetDexClassDefIndex() };
|
||
|
return ref_ == test;
|
||
|
}
|
||
|
|
||
|
void SetInterestingObject(ObjPtr<mirror::Object> obj)
|
||
|
REQUIRES_SHARED(art::Locks::mutator_lock_) {
|
||
|
std::lock_guard<std::mutex> lock(ref_guard_);
|
||
|
ObjPtr<mirror::Class> k = obj->AsClass();
|
||
|
ref_ = { &k->GetDexFile(), k->GetDexClassDefIndex() };
|
||
|
}
|
||
|
|
||
|
void MonitorContendedLocking(Monitor* mon ATTRIBUTE_UNUSED) override
|
||
|
REQUIRES_SHARED(Locks::mutator_lock_) { }
|
||
|
|
||
|
void MonitorContendedLocked(Monitor* mon ATTRIBUTE_UNUSED) override
|
||
|
REQUIRES_SHARED(Locks::mutator_lock_) { }
|
||
|
|
||
|
void ObjectWaitStart(Handle<mirror::Object> obj, int64_t millis ATTRIBUTE_UNUSED) override
|
||
|
REQUIRES_SHARED(Locks::mutator_lock_) {
|
||
|
if (IsInterestingObject(obj.Get())) {
|
||
|
saw_wait_start_ = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void MonitorWaitFinished(Monitor* m, bool timed_out ATTRIBUTE_UNUSED) override
|
||
|
REQUIRES_SHARED(Locks::mutator_lock_) {
|
||
|
if (IsInterestingObject(m->GetObject())) {
|
||
|
saw_wait_finished_ = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
std::mutex ref_guard_;
|
||
|
ClassReference ref_ = {nullptr, 0};
|
||
|
bool saw_wait_start_ = false;
|
||
|
bool saw_wait_finished_ = false;
|
||
|
};
|
||
|
|
||
|
Callback cb_;
|
||
|
};
|
||
|
|
||
|
// TODO It would be good to have more tests for this but due to the multi-threaded nature of the
|
||
|
// callbacks this is difficult. For now the run-tests 1931 & 1932 should be sufficient.
|
||
|
TEST_F(MonitorWaitCallbacksTest, WaitUnlocked) {
|
||
|
ASSERT_FALSE(cb_.saw_wait_finished_);
|
||
|
ASSERT_FALSE(cb_.saw_wait_start_);
|
||
|
{
|
||
|
Thread* self = Thread::Current();
|
||
|
self->TransitionFromSuspendedToRunnable();
|
||
|
bool started = runtime_->Start();
|
||
|
ASSERT_TRUE(started);
|
||
|
{
|
||
|
ScopedObjectAccess soa(self);
|
||
|
cb_.SetInterestingObject(
|
||
|
soa.Decode<mirror::Class>(WellKnownClasses::java_util_Collections));
|
||
|
Monitor::Wait(
|
||
|
self,
|
||
|
// Just a random class
|
||
|
soa.Decode<mirror::Class>(WellKnownClasses::java_util_Collections),
|
||
|
/*ms=*/0,
|
||
|
/*ns=*/0,
|
||
|
/*interruptShouldThrow=*/false,
|
||
|
/*why=*/kWaiting);
|
||
|
}
|
||
|
}
|
||
|
ASSERT_TRUE(cb_.saw_wait_start_);
|
||
|
ASSERT_FALSE(cb_.saw_wait_finished_);
|
||
|
}
|
||
|
|
||
|
} // namespace art
|