blob: 9d371b73f6f43b6529303ce9c05d6b580fed70e1 [file] [log] [blame]
#include "aos/mutex/mutex.h"
#include <math.h>
#include <pthread.h>
#include <sched.h>
#include <chrono>
#include <thread>
#include "gtest/gtest.h"
#include "aos/die.h"
#include "aos/time/time.h"
#include "aos/util/death_test_log_implementation.h"
#include "aos/util/thread.h"
#include "aos/ipc_lib/aos_sync.h"
#include "aos/ipc_lib/core_lib.h"
#include "aos/testing/test_logging.h"
#include "aos/testing/test_shm.h"
namespace aos {
namespace testing {
namespace chrono = ::std::chrono;
namespace this_thread = ::std::this_thread;
class MutexTest : public ::testing::Test {
public:
Mutex test_mutex_;
protected:
void SetUp() override {
::aos::testing::EnableTestLogging();
SetDieTestMode(true);
}
};
typedef MutexTest MutexDeathTest;
typedef MutexTest MutexLockerTest;
typedef MutexTest MutexLockerDeathTest;
typedef MutexTest IPCMutexLockerTest;
typedef MutexTest IPCMutexLockerDeathTest;
typedef MutexTest IPCRecursiveMutexLockerTest;
TEST_F(MutexTest, TryLock) {
EXPECT_EQ(Mutex::State::kLocked, test_mutex_.TryLock());
EXPECT_EQ(Mutex::State::kLockFailed, test_mutex_.TryLock());
test_mutex_.Unlock();
}
TEST_F(MutexTest, Lock) {
ASSERT_FALSE(test_mutex_.Lock());
EXPECT_EQ(Mutex::State::kLockFailed, test_mutex_.TryLock());
test_mutex_.Unlock();
}
TEST_F(MutexTest, Unlock) {
ASSERT_FALSE(test_mutex_.Lock());
EXPECT_EQ(Mutex::State::kLockFailed, test_mutex_.TryLock());
test_mutex_.Unlock();
EXPECT_EQ(Mutex::State::kLocked, test_mutex_.TryLock());
test_mutex_.Unlock();
}
// Sees what happens with multiple unlocks.
TEST_F(MutexDeathTest, RepeatUnlock) {
logging::Init();
ASSERT_FALSE(test_mutex_.Lock());
test_mutex_.Unlock();
EXPECT_DEATH(
{
logging::AddImplementation(new util::DeathTestLogImplementation());
test_mutex_.Unlock();
},
".*multiple unlock.*");
}
// Sees what happens if you unlock without ever locking (or unlocking) it.
TEST_F(MutexDeathTest, NeverLock) {
logging::Init();
EXPECT_DEATH(
{
logging::AddImplementation(new util::DeathTestLogImplementation());
test_mutex_.Unlock();
},
".*multiple unlock.*");
}
// Tests that locking a mutex multiple times from the same thread fails nicely.
TEST_F(MutexDeathTest, RepeatLock) {
EXPECT_DEATH(
{
logging::AddImplementation(new util::DeathTestLogImplementation());
ASSERT_FALSE(test_mutex_.Lock());
ASSERT_FALSE(test_mutex_.Lock());
},
".*multiple lock.*");
}
// Tests that Lock behaves correctly when the previous owner exits with the lock
// held (which is the same as dying any other way).
TEST_F(MutexTest, OwnerDiedDeathLock) {
testing::TestSharedMemory my_shm;
Mutex *mutex =
static_cast<Mutex *>(shm_malloc_aligned(sizeof(Mutex), alignof(Mutex)));
new (mutex) Mutex();
util::FunctionThread::RunInOtherThread([&]() {
ASSERT_FALSE(mutex->Lock());
});
EXPECT_TRUE(mutex->Lock());
mutex->Unlock();
mutex->~Mutex();
}
// Tests that TryLock behaves correctly when the previous owner dies.
TEST_F(MutexTest, OwnerDiedDeathTryLock) {
testing::TestSharedMemory my_shm;
Mutex *mutex =
static_cast<Mutex *>(shm_malloc_aligned(sizeof(Mutex), alignof(Mutex)));
new (mutex) Mutex();
util::FunctionThread::RunInOtherThread([&]() {
ASSERT_FALSE(mutex->Lock());
});
EXPECT_EQ(Mutex::State::kOwnerDied, mutex->TryLock());
mutex->Unlock();
mutex->~Mutex();
}
// TODO(brians): Test owner dying by being SIGKILLed and SIGTERMed.
// This sequence of mutex operations used to mess up the robust list and cause
// one of the mutexes to not get owner-died like it should.
TEST_F(MutexTest, DontCorruptRobustList) {
// I think this was the allocator lock in the original failure.
Mutex mutex1;
// This one should get owner-died afterwards (iff the kernel accepts the
// robust list and uses it). I think it was the task_death_notification lock
// in the original failure.
Mutex mutex2;
util::FunctionThread::RunInOtherThread([&]() {
ASSERT_FALSE(mutex1.Lock());
ASSERT_FALSE(mutex2.Lock());
mutex1.Unlock();
});
EXPECT_EQ(Mutex::State::kLocked, mutex1.TryLock());
EXPECT_EQ(Mutex::State::kOwnerDied, mutex2.TryLock());
mutex1.Unlock();
mutex2.Unlock();
}
namespace {
class AdderThread : public ::aos::util::Thread {
public:
AdderThread(int *counter, Mutex *mutex,
monotonic_clock::duration sleep_before_time,
monotonic_clock::duration sleep_after_time)
: counter_(counter),
mutex_(mutex),
sleep_before_time_(sleep_before_time),
sleep_after_time_(sleep_after_time) {}
private:
virtual void Run() override {
this_thread::sleep_for(sleep_before_time_);
MutexLocker locker(mutex_);
++(*counter_);
this_thread::sleep_for(sleep_after_time_);
}
int *const counter_;
Mutex *const mutex_;
const monotonic_clock::duration sleep_before_time_, sleep_after_time_;
};
} // namespace
// Verifies that ThreadSanitizer understands that a contended mutex establishes
// a happens-before relationship.
TEST_F(MutexTest, ThreadSanitizerContended) {
int counter = 0;
AdderThread threads[2]{
{&counter, &test_mutex_, chrono::milliseconds(200),
chrono::milliseconds(0)},
{&counter, &test_mutex_, chrono::milliseconds(0),
chrono::milliseconds(0)},
};
for (auto &c : threads) {
c.Start();
}
for (auto &c : threads) {
c.WaitUntilDone();
}
EXPECT_EQ(2, counter);
}
// Verifiers that ThreadSanitizer understands how a mutex works.
// For some reason this used to fail when the other tests didn't...
// The loops make it fail more reliably when it's going to.
TEST_F(MutexTest, ThreadSanitizerMutexLocker) {
for (int i = 0; i < 100; ++i) {
int counter = 0;
::std::thread thread([&counter, this]() {
for (int i = 0; i < 300; ++i) {
MutexLocker locker(&test_mutex_);
++counter;
}
});
for (int i = 0; i < 300; ++i) {
MutexLocker locker(&test_mutex_);
--counter;
}
thread.join();
EXPECT_EQ(0, counter);
}
}
// Verifies that ThreadSanitizer understands that an uncontended mutex
// establishes a happens-before relationship.
TEST_F(MutexTest, ThreadSanitizerUncontended) {
int counter = 0;
AdderThread threads[2]{
{&counter, &test_mutex_, chrono::milliseconds(0),
chrono::milliseconds(0)},
{&counter, &test_mutex_, chrono::milliseconds(200),
chrono::milliseconds(0)}, };
for (auto &c : threads) {
c.Start();
}
for (auto &c : threads) {
c.WaitUntilDone();
}
EXPECT_EQ(2, counter);
}
namespace {
class LockerThread : public util::Thread {
public:
LockerThread(Mutex *mutex, bool lock, bool unlock)
: mutex_(mutex), lock_(lock), unlock_(unlock) {}
private:
virtual void Run() override {
if (lock_) ASSERT_FALSE(mutex_->Lock());
if (unlock_) mutex_->Unlock();
}
Mutex *const mutex_;
const bool lock_, unlock_;
};
} // namespace
// Makes sure that we don't SIGSEGV or something with multiple threads.
TEST_F(MutexTest, MultiThreadedLock) {
LockerThread t(&test_mutex_, true, true);
t.Start();
ASSERT_FALSE(test_mutex_.Lock());
test_mutex_.Unlock();
t.Join();
}
TEST_F(MutexLockerTest, Basic) {
{
aos::MutexLocker locker(&test_mutex_);
EXPECT_EQ(Mutex::State::kLockFailed, test_mutex_.TryLock());
}
EXPECT_EQ(Mutex::State::kLocked, test_mutex_.TryLock());
test_mutex_.Unlock();
}
// Tests that MutexLocker behaves correctly when the previous owner dies.
TEST_F(MutexLockerDeathTest, OwnerDied) {
testing::TestSharedMemory my_shm;
Mutex *mutex =
static_cast<Mutex *>(shm_malloc_aligned(sizeof(Mutex), alignof(Mutex)));
new (mutex) Mutex();
util::FunctionThread::RunInOtherThread([&]() {
ASSERT_FALSE(mutex->Lock());
});
EXPECT_DEATH(
{
logging::AddImplementation(new util::DeathTestLogImplementation());
MutexLocker locker(mutex);
},
".*previous owner of mutex [^ ]+ died.*");
mutex->~Mutex();
}
TEST_F(IPCMutexLockerTest, Basic) {
{
aos::IPCMutexLocker locker(&test_mutex_);
EXPECT_EQ(Mutex::State::kLockFailed, test_mutex_.TryLock());
EXPECT_FALSE(locker.owner_died());
}
EXPECT_EQ(Mutex::State::kLocked, test_mutex_.TryLock());
test_mutex_.Unlock();
}
// Tests what happens when the caller doesn't check if the previous owner died
// with an IPCMutexLocker.
TEST_F(IPCMutexLockerDeathTest, NoCheckOwnerDied) {
EXPECT_DEATH({ aos::IPCMutexLocker locker(&test_mutex_); },
"nobody checked if the previous owner of mutex [^ ]+ died.*");
}
TEST_F(IPCRecursiveMutexLockerTest, Basic) {
{
aos::IPCRecursiveMutexLocker locker(&test_mutex_);
EXPECT_EQ(Mutex::State::kLockFailed, test_mutex_.TryLock());
EXPECT_FALSE(locker.owner_died());
}
EXPECT_EQ(Mutex::State::kLocked, test_mutex_.TryLock());
test_mutex_.Unlock();
}
// Tests actually locking a mutex recursively with IPCRecursiveMutexLocker.
TEST_F(IPCRecursiveMutexLockerTest, RecursiveLock) {
{
aos::IPCRecursiveMutexLocker locker(&test_mutex_);
EXPECT_EQ(Mutex::State::kLockFailed, test_mutex_.TryLock());
{
aos::IPCRecursiveMutexLocker locker(&test_mutex_);
EXPECT_EQ(Mutex::State::kLockFailed, test_mutex_.TryLock());
EXPECT_FALSE(locker.owner_died());
}
EXPECT_EQ(Mutex::State::kLockFailed, test_mutex_.TryLock());
EXPECT_FALSE(locker.owner_died());
}
EXPECT_EQ(Mutex::State::kLocked, test_mutex_.TryLock());
test_mutex_.Unlock();
}
// Tests that IPCMutexLocker behaves correctly when the previous owner dies.
TEST_F(IPCMutexLockerTest, OwnerDied) {
testing::TestSharedMemory my_shm;
Mutex *mutex =
static_cast<Mutex *>(shm_malloc_aligned(sizeof(Mutex), alignof(Mutex)));
new (mutex) Mutex();
util::FunctionThread::RunInOtherThread([&]() {
ASSERT_FALSE(mutex->Lock());
});
{
aos::IPCMutexLocker locker(mutex);
EXPECT_EQ(Mutex::State::kLockFailed, mutex->TryLock());
EXPECT_TRUE(locker.owner_died());
}
EXPECT_EQ(Mutex::State::kLocked, mutex->TryLock());
mutex->Unlock();
mutex->~Mutex();
}
} // namespace testing
} // namespace aos