blob: b9d9ae592ffb709cc952e4880177a228da6a6990 [file]
/*
* Copyright (C) 2012 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 "base/mutex.h"
#include "base/locks.h"
#include "mutex-inl.h"
#include "common_runtime_test.h"
#include "thread-current-inl.h"
#include "thread.h"
#include "thread_list.h"
// The standard ASSERT macros don't play correctly with thread-safety analysis, in that they
// apparently do not always execute the rest of the function, and can thus result in function
// returns with different lock states. They should only be used when no locks are held.
//
// In other contexts, we replace assertions by macros that record failures as we go, but only
// reporting them at the end. We invoke CHECK_DEFERRED_FAILURE_STATE only when no locks are held.
#define DECLARE_DEFERRED_FAILURE_STATE \
int fail_line = 0; \
const char* fail_cond = nullptr;
#define DEFERRED_ASSERT_TRUE(p) \
if (!(p) && fail_line == 0) { \
fail_line = __LINE__; \
fail_cond = #p; \
}
#define DEFERRED_ASSERT_FALSE(p) DEFERRED_ASSERT_TRUE(!p)
#define CHECK_DEFERRED_FAILURE_STATE CHECK_EQ(fail_line, 0) << fail_cond
namespace art HIDDEN {
class MutexTest : public CommonRuntimeTest {
protected:
MutexTest() {
use_boot_image_ = true; // Make the Runtime creation cheaper.
}
};
// TODO: The use of Assert...Held() here is not optimal, since that tells the thread checker
// to assume it is held, which is not great in a test, and it actually performs the dynamic
// check only in debug builds, which is also not great in a test. Use ASSERT_TRUE(...Is...Held())
// or DEFERRED_ASSERT_TRUE(...Is...Held()) instead, as we've started to do below.
struct MutexTester {
static void AssertDepth(Mutex& mu, uint32_t expected_depth) {
ASSERT_EQ(expected_depth, mu.GetDepth());
// This test is single-threaded, so we also know _who_ should hold the lock.
if (expected_depth == 0) {
mu.AssertNotHeld(Thread::Current());
} else {
mu.AssertHeld(Thread::Current());
}
}
};
TEST_F(MutexTest, LockUnlock) {
// TODO: Remove `Mutex` dependency on `Runtime` or at least make sure it works
// without a `Runtime` with reasonable defaults (and without dumping stack for timeout).
ASSERT_TRUE(Runtime::Current() != nullptr);
Mutex mu("test mutex");
MutexTester::AssertDepth(mu, 0U);
mu.Lock(Thread::Current());
MutexTester::AssertDepth(mu, 1U);
mu.Unlock(Thread::Current());
MutexTester::AssertDepth(mu, 0U);
}
// TODO: NO_THREAD_SAFETY_ANALYSIS because ASSERT_TRUE is not yet DEFERRED.
static void TryLockUnlockTest() NO_THREAD_SAFETY_ANALYSIS {
Mutex mu("test mutex");
MutexTester::AssertDepth(mu, 0U);
ASSERT_TRUE(mu.TryLock(Thread::Current()));
MutexTester::AssertDepth(mu, 1U);
mu.Unlock(Thread::Current());
MutexTester::AssertDepth(mu, 0U);
}
TEST_F(MutexTest, TryLockUnlock) {
TryLockUnlockTest();
}
// Assertions here don't play with thread safety analysis.
static void RecursiveLockUnlockTest() NO_THREAD_SAFETY_ANALYSIS {
Mutex mu("test mutex", kDefaultMutexLevel, true);
MutexTester::AssertDepth(mu, 0U);
mu.Lock(Thread::Current());
MutexTester::AssertDepth(mu, 1U);
mu.Lock(Thread::Current());
MutexTester::AssertDepth(mu, 2U);
mu.Unlock(Thread::Current());
MutexTester::AssertDepth(mu, 1U);
mu.Unlock(Thread::Current());
MutexTester::AssertDepth(mu, 0U);
}
TEST_F(MutexTest, RecursiveLockUnlock) {
RecursiveLockUnlockTest();
}
static void RecursiveTryLockUnlockTest() NO_THREAD_SAFETY_ANALYSIS {
Mutex mu("test mutex", kDefaultMutexLevel, true);
MutexTester::AssertDepth(mu, 0U);
ASSERT_TRUE(mu.TryLock(Thread::Current()));
MutexTester::AssertDepth(mu, 1U);
ASSERT_TRUE(mu.TryLock(Thread::Current()));
MutexTester::AssertDepth(mu, 2U);
mu.Unlock(Thread::Current());
MutexTester::AssertDepth(mu, 1U);
mu.Unlock(Thread::Current());
MutexTester::AssertDepth(mu, 0U);
}
TEST_F(MutexTest, RecursiveTryLockUnlock) {
RecursiveTryLockUnlockTest();
}
struct RecursiveLockWait {
RecursiveLockWait()
: mu("test mutex", kDefaultMutexLevel, true), cv("test condition variable", mu) {
}
Mutex mu;
ConditionVariable cv;
};
static void* RecursiveLockWaitCallback(void* arg) {
RecursiveLockWait* state = reinterpret_cast<RecursiveLockWait*>(arg);
state->mu.Lock(Thread::Current());
state->cv.Signal(Thread::Current());
state->mu.Unlock(Thread::Current());
return nullptr;
}
// Recursive lock acquisition is not supported by our current thread-safety annotations, which do
// not mention REENTRANT_CAPABILITY. This may not be worth fixing, since reentrant Mutexes should
// probably be deprecated.
static void RecursiveLockWaitTest() NO_THREAD_SAFETY_ANALYSIS {
DECLARE_DEFERRED_FAILURE_STATE;
RecursiveLockWait state;
state.mu.Lock(Thread::Current());
state.mu.Lock(Thread::Current());
pthread_t pthread;
int pthread_create_result = pthread_create(&pthread, nullptr, RecursiveLockWaitCallback, &state);
DEFERRED_ASSERT_TRUE(pthread_create_result == 0);
state.cv.Wait(Thread::Current());
state.mu.Unlock(Thread::Current());
state.mu.Unlock(Thread::Current());
CHECK_DEFERRED_FAILURE_STATE;
EXPECT_EQ(pthread_join(pthread, nullptr), 0);
}
// This ensures we don't hang when waiting on a recursively locked mutex,
// which is not supported with bare pthread_mutex_t.
TEST_F(MutexTest, RecursiveLockWait) {
RecursiveLockWaitTest();
}
TEST_F(MutexTest, SharedLockUnlock) {
DECLARE_DEFERRED_FAILURE_STATE;
ReaderWriterMutex mu("test rwmutex");
DEFERRED_ASSERT_FALSE(mu.IsSharedHeld(Thread::Current()));
DEFERRED_ASSERT_FALSE(mu.IsExclusiveHeld(Thread::Current()));
mu.SharedLock(Thread::Current());
DEFERRED_ASSERT_TRUE(mu.IsSharedHeld(Thread::Current()));
DEFERRED_ASSERT_FALSE(mu.IsExclusiveHeld(Thread::Current()));
mu.SharedUnlock(Thread::Current());
DEFERRED_ASSERT_FALSE(mu.IsSharedHeld(Thread::Current()));
DEFERRED_ASSERT_FALSE(mu.IsExclusiveHeld(Thread::Current()));
CHECK_DEFERRED_FAILURE_STATE;
}
TEST_F(MutexTest, ExclusiveLockUnlock) {
DECLARE_DEFERRED_FAILURE_STATE;
ReaderWriterMutex mu("test rwmutex");
mu.AssertNotHeld(Thread::Current());
mu.AssertNotExclusiveHeld(Thread::Current());
mu.ExclusiveLock(Thread::Current());
DEFERRED_ASSERT_TRUE(mu.IsSharedHeld(Thread::Current()));
DEFERRED_ASSERT_TRUE(mu.IsExclusiveHeld(Thread::Current()));
mu.ExclusiveUnlock(Thread::Current());
DEFERRED_ASSERT_FALSE(mu.IsSharedHeld(Thread::Current()));
DEFERRED_ASSERT_FALSE(mu.IsExclusiveHeld(Thread::Current()));
CHECK_DEFERRED_FAILURE_STATE;
// This is technically not guaranteed, but it should always succeed. We try to keep the control
// flow in the failure case simple enough so that thread-safety analysis can understand it.
if (!mu.ExclusiveTryLock(Thread::Current())) {
abort();
}
DEFERRED_ASSERT_TRUE(mu.IsSharedHeld(Thread::Current()));
DEFERRED_ASSERT_TRUE(mu.IsExclusiveHeld(Thread::Current()));
mu.ExclusiveUnlock(Thread::Current());
ASSERT_FALSE(mu.IsExclusiveHeld(Thread::Current()));
ASSERT_FALSE(mu.IsSharedHeld(Thread::Current()));
CHECK_DEFERRED_FAILURE_STATE;
mu.ExclusiveLock(Thread::Current());
DEFERRED_ASSERT_TRUE(mu.IsSharedHeld(Thread::Current()));
DEFERRED_ASSERT_TRUE(mu.IsExclusiveHeld(Thread::Current()));
mu.Downgrade(Thread::Current());
DEFERRED_ASSERT_TRUE(mu.IsSharedHeld(Thread::Current()));
DEFERRED_ASSERT_FALSE(mu.IsExclusiveHeld(Thread::Current()));
mu.SharedUnlock(Thread::Current());
DEFERRED_ASSERT_FALSE(mu.IsSharedHeld(Thread::Current()));
DEFERRED_ASSERT_FALSE(mu.IsExclusiveHeld(Thread::Current()));
CHECK_DEFERRED_FAILURE_STATE;
}
static void SharedTryLockUnlockTest() {
ReaderWriterMutex mu("test rwmutex");
mu.AssertNotHeld(Thread::Current());
ASSERT_TRUE(mu.SharedTryLock(Thread::Current()));
mu.AssertSharedHeld(Thread::Current());
mu.SharedUnlock(Thread::Current());
mu.AssertNotHeld(Thread::Current());
}
TEST_F(MutexTest, SharedTryLockUnlock) {
SharedTryLockUnlockTest();
}
class ScopedVirtualThreadId {
public:
ScopedVirtualThreadId(): id_(AllocThreadId()) {}
~ScopedVirtualThreadId() {
ThreadList* thread_list = Runtime::Current()->GetThreadList();
thread_list->ReleaseVirtualThreadSuspendCount(id_);
thread_list->ReleaseThreadId(Thread::Current(), id_);
}
uint32_t GetId() const {
return id_;
}
private:
static uint32_t AllocThreadId() {
ThreadList* thread_list = Runtime::Current()->GetThreadList();
Thread* self = Thread::Current();
uint32_t id = thread_list->AllocThreadId(self);
thread_list->AllocVirtualThreadSuspendCount(id);
return id;
}
private:
const uint32_t id_;
};
class VirtualThreadMounter {
public:
explicit VirtualThreadMounter(MountedVirtualThreadData* mounted_data)
REQUIRES_SHARED(Locks::mutator_lock_) {
Thread* self = Thread::Current();
self->TrySetMountedVirtualThreadData(mounted_data);
}
~VirtualThreadMounter() {
Thread* self = Thread::Current();
self->TryClearMountedVirtualThreadData();
}
};
TEST_F(MutexTest, MonitorLockUnlock) {
ASSERT_TRUE(Runtime::Current() != nullptr);
Thread* self = Thread::Current();
pid_t tid = self->GetTid();
uint32_t carrier_id = self->GetThreadId();
MonitorMutex mu;
ASSERT_EQ(tid, mu.GetSelfId(self));
MutexTester::AssertDepth(mu, 0U);
mu.Lock(self);
MutexTester::AssertDepth(mu, 1U);
mu.Unlock(self);
MutexTester::AssertDepth(mu, 0U);
if (!kIsVirtualThreadEnabled) {
return;
}
ScopedVirtualThreadId virtual_thread_id_holder;
uint32_t virtual_thread_id = virtual_thread_id_holder.GetId();
ASSERT_NE(virtual_thread_id, carrier_id);
MountedVirtualThreadData mounted_data(virtual_thread_id, carrier_id, 0);
ScopedObjectAccess soa(self);
VirtualThreadMounter mounter(&mounted_data);
ASSERT_EQ(virtual_thread_id, self->GetVirtualThreadId());
ASSERT_EQ(virtual_thread_id, self->GetMonitorThreadId());
ASSERT_EQ(virtual_thread_id | MonitorMutex::kVTFlag, mu.GetSelfId(self));
MutexTester::AssertDepth(mu, 0U);
mu.Lock(self);
MutexTester::AssertDepth(mu, 1U);
mu.Unlock(self);
MutexTester::AssertDepth(mu, 0U);
}
} // namespace art