add IPCRecursiveMutexLocker + tests

Change-Id: Ie7fca7032266935278e646534bea1180136fd3d0
diff --git a/aos/common/mutex.h b/aos/common/mutex.h
index db1b012..b952644 100644
--- a/aos/common/mutex.h
+++ b/aos/common/mutex.h
@@ -43,6 +43,10 @@
   // 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_;
 
@@ -101,6 +105,36 @@
   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_COMMON_MUTEX_H_