Removed Common
Change-Id: I01ea8f07220375c2ad9bc0092281d4f27c642303
diff --git a/aos/mutex/BUILD b/aos/mutex/BUILD
new file mode 100644
index 0000000..02cec57
--- /dev/null
+++ b/aos/mutex/BUILD
@@ -0,0 +1,38 @@
+package(default_visibility = ["//visibility:public"])
+
+cc_library(
+ name = "mutex",
+ srcs = [
+ "mutex.cc",
+ ],
+ hdrs = [
+ "mutex.h",
+ ],
+ compatible_with = [
+ "//tools:armhf-debian",
+ ],
+ deps = [
+ "//aos:die",
+ "//aos/type_traits:type_traits",
+ "//aos/logging",
+ "//aos/linux_code/ipc_lib:aos_sync",
+ ],
+)
+
+cc_test(
+ name = "mutex_test",
+ srcs = [
+ "mutex_test.cc",
+ ],
+ deps = [
+ "//aos:die",
+ ":mutex",
+ "//aos/time:time",
+ "//aos/logging",
+ "//aos/util:death_test_log_implementation",
+ "//aos/util:thread",
+ "//aos/testing:googletest",
+ "//aos/testing:test_logging",
+ "//aos/testing:test_shm",
+ ],
+)
diff --git a/aos/mutex/mutex.cc b/aos/mutex/mutex.cc
new file mode 100644
index 0000000..a35f0cd
--- /dev/null
+++ b/aos/mutex/mutex.cc
@@ -0,0 +1,50 @@
+#include "aos/mutex/mutex.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "aos/type_traits/type_traits.h"
+#include "aos/logging/logging.h"
+
+namespace aos {
+
+// Lock and Unlock use the return values of mutex_lock/mutex_unlock
+// to determine whether the lock/unlock succeeded.
+
+bool Mutex::Lock() {
+ const int ret = mutex_grab(&impl_);
+ if (ret == 0) {
+ return false;
+ } else if (ret == 1) {
+ return true;
+ } else {
+ LOG(FATAL, "mutex_grab(%p(=%" PRIu32 ")) failed with %d\n",
+ &impl_, impl_.futex, ret);
+ }
+}
+
+void Mutex::Unlock() {
+ mutex_unlock(&impl_);
+}
+
+Mutex::State Mutex::TryLock() {
+ const int ret = mutex_trylock(&impl_);
+ switch (ret) {
+ case 0:
+ return State::kLocked;
+ case 1:
+ return State::kOwnerDied;
+ case 4:
+ return State::kLockFailed;
+ default:
+ LOG(FATAL, "mutex_trylock(%p(=%" PRIu32 ")) failed with %d\n",
+ &impl_, impl_.futex, ret);
+ }
+}
+
+bool Mutex::OwnedBySelf() const {
+ return mutex_islocked(&impl_);
+}
+
+} // namespace aos
diff --git a/aos/mutex/mutex.h b/aos/mutex/mutex.h
new file mode 100644
index 0000000..8bd986c
--- /dev/null
+++ b/aos/mutex/mutex.h
@@ -0,0 +1,150 @@
+#ifndef AOS_MUTEX_H_
+#define AOS_MUTEX_H_
+
+#include "aos/macros.h"
+#include "aos/type_traits/type_traits.h"
+#include "aos/die.h"
+#include "aos/linux_code/ipc_lib/aos_sync.h"
+
+namespace aos {
+
+class Condition;
+
+// An abstraction of a mutex that is easy to implement for environments other
+// than Linux too.
+// If there are multiple threads or processes contending for the mutex,
+// higher priority ones will succeed in locking first,
+// and tasks of equal priorities have the same chance of getting the lock.
+// To deal with priority inversion, the linux implementation does priority
+// inheritance.
+// Before destroying a mutex, it is important to make sure it isn't locked.
+// Otherwise, the destructor will LOG(FATAL).
+class Mutex {
+ public:
+ // States that signify the result of TryLock.
+ enum class State {
+ // The mutex was acquired successfully.
+ kLocked,
+ // TryLock tried to grab the mutex and failed.
+ kLockFailed,
+ // The previous owner of the mutex died.
+ kOwnerDied,
+ };
+
+ // Creates an unlocked mutex.
+ Mutex() : impl_() {
+ static_assert(shm_ok<Mutex>::value,
+ "Mutex is not safe for use in shared memory.");
+ }
+ // Verifies that it isn't locked.
+ //
+ // This is important because freeing a locked mutex means there is freed
+ // memory in the middle of the robust list, which breaks things horribly.
+ ~Mutex() = default;
+
+ // Locks the mutex. If it fails, it calls LOG(FATAL).
+ // Returns true if the previous owner died instead of unlocking nicely.
+ bool Lock() __attribute__((warn_unused_result));
+ // Unlocks the mutex. Fails like Lock.
+ // Multiple unlocking is undefined.
+ void Unlock();
+ // Locks the mutex unless it is already locked.
+ // Returns the new state of the mutex.
+ // Doesn't wait for the mutex to be unlocked if it is locked.
+ State TryLock() __attribute__((warn_unused_result));
+
+ // Returns true iff the current task has this mutex locked.
+ // This is mainly for IPCRecursiveMutexLocker to use.
+ bool OwnedBySelf() const;
+
+ private:
+ aos_mutex impl_;
+
+ friend class Condition; // for access to impl_
+};
+
+// A class that locks a Mutex when constructed and unlocks it when destructed.
+// Designed to be used as a local variable so that
+// the mutex will be unlocked when the scope is exited.
+// This one immediately Dies if the previous owner died. This makes it a good
+// choice for mutexes that are only used within a single process, but NOT for
+// mutexes shared by multiple processes. For those, use IPCMutexLocker.
+class MutexLocker {
+ public:
+ explicit MutexLocker(Mutex *mutex) : mutex_(mutex) {
+ if (__builtin_expect(mutex_->Lock(), false)) {
+ ::aos::Die("previous owner of mutex %p died but it shouldn't be able to",
+ this);
+ }
+ }
+ ~MutexLocker() {
+ mutex_->Unlock();
+ }
+
+ private:
+ Mutex *const mutex_;
+
+ DISALLOW_COPY_AND_ASSIGN(MutexLocker);
+};
+
+// A version of MutexLocker which reports the previous owner dying instead of
+// immediately LOG(FATAL)ing.
+class IPCMutexLocker {
+ public:
+ explicit IPCMutexLocker(Mutex *mutex)
+ : mutex_(mutex), owner_died_(mutex_->Lock()) {}
+ ~IPCMutexLocker() {
+ if (__builtin_expect(!owner_died_checked_, false)) {
+ ::aos::Die("nobody checked if the previous owner of mutex %p died", this);
+ }
+ mutex_->Unlock();
+ }
+
+ // Whether or not the previous owner died. If this is not called at least
+ // once, the destructor will ::aos::Die.
+ __attribute__((warn_unused_result)) bool owner_died() {
+ owner_died_checked_ = true;
+ return __builtin_expect(owner_died_, false);
+ }
+
+ private:
+ Mutex *const mutex_;
+ const bool owner_died_;
+ bool owner_died_checked_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(IPCMutexLocker);
+};
+
+// A version of IPCMutexLocker which only locks (and unlocks) the mutex if the
+// current task does not already hold it.
+class IPCRecursiveMutexLocker {
+ public:
+ explicit IPCRecursiveMutexLocker(Mutex *mutex)
+ : mutex_(mutex),
+ locked_(!mutex_->OwnedBySelf()),
+ owner_died_(locked_ ? mutex_->Lock() : false) {}
+ ~IPCRecursiveMutexLocker() {
+ if (__builtin_expect(!owner_died_checked_, false)) {
+ ::aos::Die("nobody checked if the previous owner of mutex %p died", this);
+ }
+ if (locked_) mutex_->Unlock();
+ }
+
+ // Whether or not the previous owner died. If this is not called at least
+ // once, the destructor will ::aos::Die.
+ __attribute__((warn_unused_result)) bool owner_died() {
+ owner_died_checked_ = true;
+ return __builtin_expect(owner_died_, false);
+ }
+
+ private:
+ Mutex *const mutex_;
+ const bool locked_, owner_died_;
+ bool owner_died_checked_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(IPCRecursiveMutexLocker);
+};
+
+} // namespace aos
+
+#endif // AOS_MUTEX_H_
diff --git a/aos/mutex/mutex_test.cc b/aos/mutex/mutex_test.cc
new file mode 100644
index 0000000..8e3daac
--- /dev/null
+++ b/aos/mutex/mutex_test.cc
@@ -0,0 +1,373 @@
+#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/linux_code/ipc_lib/aos_sync.h"
+#include "aos/linux_code/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