aos: Refactor lockless_queue ownership into a class

This sets us up to make it more robust with a safer API.  Our end goal
is to more reliably detect thread deaths.

Change-Id: I00fad59a4d77eec31f0f51c85834b0892d2d2462
Co-authored-By: Austin Schuh <austin.schuh@bluerivertech.com>
Signed-off-by: James Kuszmaul <james.kuszmaul@bluerivertech.com>
diff --git a/aos/ipc_lib/BUILD b/aos/ipc_lib/BUILD
index d67616f..d7dee64 100644
--- a/aos/ipc_lib/BUILD
+++ b/aos/ipc_lib/BUILD
@@ -193,10 +193,12 @@
         "lockless_queue.cc",
         "lockless_queue_memory.h",
         "memory_mapped_queue.cc",
+        "robust_ownership_tracker.cc",
     ],
     hdrs = [
         "lockless_queue.h",
         "memory_mapped_queue.h",
+        "robust_ownership_tracker.h",
     ],
     target_compatible_with = ["@platforms//os:linux"],
     visibility = ["//visibility:public"],
diff --git a/aos/ipc_lib/lockless_queue.cc b/aos/ipc_lib/lockless_queue.cc
index fd3a305..0752e2f 100644
--- a/aos/ipc_lib/lockless_queue.cc
+++ b/aos/ipc_lib/lockless_queue.cc
@@ -172,9 +172,7 @@
   size_t valid_senders = 0;
   for (size_t i = 0; i < num_senders; ++i) {
     Sender *sender = memory->GetSender(i);
-    const uint32_t tid =
-        __atomic_load_n(&(sender->tid.futex), __ATOMIC_ACQUIRE);
-    if (!(tid & FUTEX_OWNER_DIED)) {
+    if (!sender->ownership_tracker.LoadAcquire().OwnerIsDead()) {
       // Not dead.
       ++valid_senders;
       continue;
@@ -227,7 +225,7 @@
       // always be a NOP if it's in 1), verified by a DCHECK.
       memory->GetMessage(scratch_index)->header.queue_index.RelaxedInvalidate();
 
-      __atomic_store_n(&(sender->tid.futex), 0, __ATOMIC_RELEASE);
+      sender->ownership_tracker.ForceClear();
       ++valid_senders;
       continue;
     }
@@ -245,7 +243,7 @@
       aos_compiler_memory_barrier();
 
       // And mark that we succeeded.
-      __atomic_store_n(&(sender->tid.futex), 0, __ATOMIC_RELEASE);
+      sender->ownership_tracker.ForceClear();
       ++valid_senders;
       continue;
     }
@@ -259,13 +257,11 @@
   // read it before it's set.
   for (size_t i = 0; i < num_pinners; ++i) {
     Pinner *const pinner = memory->GetPinner(i);
-    const uint32_t tid =
-        __atomic_load_n(&(pinner->tid.futex), __ATOMIC_ACQUIRE);
-    if (!(tid & FUTEX_OWNER_DIED)) {
+    if (!pinner->ownership_tracker.LoadAcquire().OwnerIsDead()) {
       continue;
     }
     pinner->pinned.Invalidate();
-    __atomic_store_n(&(pinner->tid.futex), 0, __ATOMIC_RELEASE);
+    pinner->ownership_tracker.ForceClear();
   }
 
   // If all the senders are (or were made) good, there is no need to do the hard
@@ -284,9 +280,7 @@
     num_missing = 0;
     for (size_t i = 0; i < num_senders; ++i) {
       Sender *const sender = memory->GetSender(i);
-      const uint32_t tid =
-          __atomic_load_n(&(sender->tid.futex), __ATOMIC_ACQUIRE);
-      if (tid & FUTEX_OWNER_DIED) {
+      if (sender->ownership_tracker.LoadAcquire().OwnerIsDead()) {
         if (!need_recovery[i]) {
           return false;
         }
@@ -331,9 +325,7 @@
     const size_t starting_num_missing = num_missing;
     for (size_t i = 0; i < num_senders; ++i) {
       Sender *sender = memory->GetSender(i);
-      const uint32_t tid =
-          __atomic_load_n(&(sender->tid.futex), __ATOMIC_ACQUIRE);
-      if (!(tid & FUTEX_OWNER_DIED)) {
+      if (!sender->ownership_tracker.LoadAcquire().OwnerIsDead()) {
         CHECK(!need_recovery[i]) << ": Somebody else recovered a sender: " << i;
         continue;
       }
@@ -368,7 +360,7 @@
             ->header.queue_index.RelaxedInvalidate();
 
         // And then mark this sender clean.
-        __atomic_store_n(&(sender->tid.futex), 0, __ATOMIC_RELEASE);
+        sender->ownership_tracker.ForceClear();
         need_recovery[i] = false;
 
         // And account for scratch_index.
@@ -394,7 +386,7 @@
         sender->to_replace.Invalidate();
 
         // And then mark this sender clean.
-        __atomic_store_n(&(sender->tid.futex), 0, __ATOMIC_RELEASE);
+        sender->ownership_tracker.ForceClear();
         need_recovery[i] = false;
 
         // And account for to_replace.
@@ -437,6 +429,14 @@
 
 }  // namespace
 
+bool PretendThatOwnerIsDeadForTesting(aos_mutex *mutex, pid_t tid) {
+  if (static_cast<pid_t>(mutex->futex & FUTEX_TID_MASK) == tid) {
+    mutex->futex = FUTEX_OWNER_DIED;
+    return true;
+  }
+  return false;
+}
+
 size_t LocklessQueueConfiguration::message_size() const {
   // Round up the message size so following data is aligned appropriately.
   // Make sure to leave space to align the message data. It will be aligned
@@ -685,11 +685,10 @@
   CHECK_NE(watcher_index_, -1);
 
   // Make sure we still own the slot we are supposed to.
-  CHECK(
-      death_notification_is_held(&(memory_->GetWatcher(watcher_index_)->tid)));
+  CHECK(memory_->GetWatcher(watcher_index_)->ownership_tracker.IsHeldBySelf());
 
   // The act of unlocking invalidates the entry.  Invalidate it.
-  death_notification_release(&(memory_->GetWatcher(watcher_index_)->tid));
+  memory_->GetWatcher(watcher_index_)->ownership_tracker.Release();
   // And internally forget the slot.
   watcher_index_ = -1;
 
@@ -699,7 +698,7 @@
   // And confirm that nothing is owned by us.
   const int num_watchers = memory_->num_watchers();
   for (int i = 0; i < num_watchers; ++i) {
-    CHECK(!death_notification_is_held(&(memory_->GetWatcher(i)->tid)))
+    CHECK(!memory_->GetWatcher(i)->ownership_tracker.IsHeldBySelf())
         << ": " << i;
   }
 }
@@ -732,14 +731,14 @@
   for (int i = 0; i < num_watchers; ++i) {
     // If we see a slot the kernel has marked as dead, everything we do reusing
     // it needs to happen-after whatever that process did before dying.
-    auto *const futex = &(memory_->GetWatcher(i)->tid.futex);
-    const uint32_t tid = __atomic_load_n(futex, __ATOMIC_ACQUIRE);
-    if (tid == 0 || (tid & FUTEX_OWNER_DIED)) {
+    auto *const ownership_tracker =
+        &(memory_->GetWatcher(i)->ownership_tracker);
+    if (ownership_tracker->LoadAcquire().IsUnclaimedOrOwnerIsDead()) {
       watcher_index_ = i;
       // Relaxed is OK here because we're the only task going to touch it
       // between here and the write in death_notification_init below (other
       // recovery is blocked by us holding the setup lock).
-      __atomic_store_n(futex, 0, __ATOMIC_RELAXED);
+      ownership_tracker->ForceClear();
       break;
     }
   }
@@ -756,7 +755,7 @@
 
   // Grabbing a mutex is a compiler and memory barrier, so nothing before will
   // get rearranged afterwords.
-  death_notification_init(&(w->tid));
+  w->ownership_tracker.Acquire();
 }
 
 LocklessQueueWakeUpper::LocklessQueueWakeUpper(LocklessQueue queue)
@@ -778,25 +777,25 @@
   // creating a pidfd is likely not RT.
   for (size_t i = 0; i < num_watchers; ++i) {
     const Watcher *w = memory_->GetWatcher(i);
-    watcher_copy_[i].tid = __atomic_load_n(&(w->tid.futex), __ATOMIC_RELAXED);
+    watcher_copy_[i].ownership_snapshot = w->ownership_tracker.LoadRelaxed();
     // Force the load of the TID to come first.
     aos_compiler_memory_barrier();
     watcher_copy_[i].pid = w->pid.load(std::memory_order_relaxed);
     watcher_copy_[i].priority = w->priority.load(std::memory_order_relaxed);
 
     // Use a priority of -1 to mean an invalid entry to make sorting easier.
-    if (watcher_copy_[i].tid & FUTEX_OWNER_DIED || watcher_copy_[i].tid == 0) {
+    if (watcher_copy_[i].ownership_snapshot.OwnerIsDead() ||
+        watcher_copy_[i].ownership_snapshot.IsUnclaimed()) {
       watcher_copy_[i].priority = -1;
     } else {
       // Ensure all of this happens after we're done looking at the pid+priority
       // in shared memory.
       aos_compiler_memory_barrier();
-      if (watcher_copy_[i].tid != static_cast<pid_t>(__atomic_load_n(
-                                      &(w->tid.futex), __ATOMIC_RELAXED))) {
+      if (watcher_copy_[i].ownership_snapshot !=
+          w->ownership_tracker.LoadRelaxed()) {
         // Confirm that the watcher hasn't been re-used and modified while we
         // read it.  If it has, mark it invalid again.
         watcher_copy_[i].priority = -1;
-        watcher_copy_[i].tid = 0;
       }
     }
   }
@@ -835,8 +834,8 @@
       // Send the signal.  Target just the thread that sent it so that we can
       // support multiple watchers in a process (when someone creates multiple
       // event loops in different threads).
-      rt_tgsigqueueinfo(watcher_copy.pid, watcher_copy.tid, kWakeupSignal,
-                        &uinfo);
+      rt_tgsigqueueinfo(watcher_copy.pid, watcher_copy.ownership_snapshot.tid(),
+                        kWakeupSignal, &uinfo);
 
       ++count;
     }
@@ -872,8 +871,7 @@
     // This doesn't need synchronization because we're the only process doing
     // initialization right now, and nobody else will be touching senders which
     // we're interested in.
-    const uint32_t tid = __atomic_load_n(&(s->tid.futex), __ATOMIC_RELAXED);
-    if (tid == 0) {
+    if (s->ownership_tracker.LoadRelaxed().IsUnclaimed()) {
       sender_index_ = i;
       break;
     }
@@ -888,7 +886,7 @@
 
   // Indicate that we are now alive by taking over the slot. If the previous
   // owner died, we still want to do this.
-  death_notification_init(&(sender->tid));
+  sender->ownership_tracker.Acquire();
 
   const Index scratch_index = sender->scratch_index.RelaxedLoad();
   Message *const message = memory_->GetMessage(scratch_index);
@@ -899,7 +897,7 @@
 LocklessQueueSender::~LocklessQueueSender() {
   if (sender_index_ != -1) {
     CHECK(memory_ != nullptr);
-    death_notification_release(&(memory_->GetSender(sender_index_)->tid));
+    memory_->GetSender(sender_index_)->ownership_tracker.Release();
   }
 }
 
@@ -1183,8 +1181,7 @@
     // This doesn't need synchronization because we're the only process doing
     // initialization right now, and nobody else will be touching pinners which
     // we're interested in.
-    const uint32_t tid = __atomic_load_n(&(p->tid.futex), __ATOMIC_RELAXED);
-    if (tid == 0) {
+    if (p->ownership_tracker.LoadRelaxed().IsUnclaimed()) {
       pinner_index_ = i;
       break;
     }
@@ -1200,7 +1197,7 @@
 
   // Indicate that we are now alive by taking over the slot. If the previous
   // owner died, we still want to do this.
-  death_notification_init(&(p->tid));
+  p->ownership_tracker.Acquire();
 }
 
 LocklessQueuePinner::~LocklessQueuePinner() {
@@ -1208,7 +1205,7 @@
     CHECK(memory_ != nullptr);
     memory_->GetPinner(pinner_index_)->pinned.Invalidate();
     aos_compiler_memory_barrier();
-    death_notification_release(&(memory_->GetPinner(pinner_index_)->tid));
+    memory_->GetPinner(pinner_index_)->ownership_tracker.Release();
   }
 }
 
@@ -1622,8 +1619,8 @@
   for (size_t i = 0; i < memory->num_senders(); ++i) {
     const Sender *s = memory->GetSender(i);
     ::std::cout << "    [" << i << "] -> Sender {" << ::std::endl;
-    ::std::cout << "      aos_mutex tid = " << PrintMutex(&s->tid)
-                << ::std::endl;
+    ::std::cout << "      RobustOwnershipTracker ownership_tracker = "
+                << s->ownership_tracker.DebugString() << ::std::endl;
     ::std::cout << "      AtomicIndex scratch_index = "
                 << s->scratch_index.Load().DebugString() << ::std::endl;
     ::std::cout << "      AtomicIndex to_replace = "
@@ -1637,8 +1634,8 @@
   for (size_t i = 0; i < memory->num_pinners(); ++i) {
     const Pinner *p = memory->GetPinner(i);
     ::std::cout << "    [" << i << "] -> Pinner {" << ::std::endl;
-    ::std::cout << "      aos_mutex tid = " << PrintMutex(&p->tid)
-                << ::std::endl;
+    ::std::cout << "      RobustOwnershipTracker ownership_tracker = "
+                << p->ownership_tracker.DebugString() << ::std::endl;
     ::std::cout << "      AtomicIndex scratch_index = "
                 << p->scratch_index.Load().DebugString() << ::std::endl;
     ::std::cout << "      AtomicIndex pinned = "
@@ -1653,8 +1650,8 @@
   for (size_t i = 0; i < memory->num_watchers(); ++i) {
     const Watcher *w = memory->GetWatcher(i);
     ::std::cout << "    [" << i << "] -> Watcher {" << ::std::endl;
-    ::std::cout << "      aos_mutex tid = " << PrintMutex(&w->tid)
-                << ::std::endl;
+    ::std::cout << "      RobustOwnershipTracker ownership_tracker = "
+                << w->ownership_tracker.DebugString() << ::std::endl;
     ::std::cout << "      pid_t pid = " << w->pid << ::std::endl;
     ::std::cout << "      int priority = " << w->priority << ::std::endl;
     ::std::cout << "    }" << ::std::endl;
diff --git a/aos/ipc_lib/lockless_queue.h b/aos/ipc_lib/lockless_queue.h
index 01f2b7c..14a11b6 100644
--- a/aos/ipc_lib/lockless_queue.h
+++ b/aos/ipc_lib/lockless_queue.h
@@ -14,6 +14,7 @@
 #include "aos/ipc_lib/aos_sync.h"
 #include "aos/ipc_lib/data_alignment.h"
 #include "aos/ipc_lib/index.h"
+#include "aos/ipc_lib/robust_ownership_tracker.h"
 #include "aos/time/time.h"
 #include "aos/uuid.h"
 
@@ -29,7 +30,7 @@
   // Note: this is only modified with the queue_setup_lock lock held, but may
   // always be read.
   // Any state modification should happen before the lock is acquired.
-  aos_mutex tid;
+  RobustOwnershipTracker ownership_tracker;
 
   // PID of the watcher.
   std::atomic<pid_t> pid;
@@ -46,7 +47,7 @@
   //
   // Note: this is only modified with the queue_setup_lock lock held, but may
   // always be read.
-  aos_mutex tid;
+  RobustOwnershipTracker ownership_tracker;
 
   // Index of the message we will be filling out.
   AtomicIndex scratch_index;
@@ -59,7 +60,7 @@
 // Structure to hold the state required to pin messages.
 struct Pinner {
   // The same as Sender::tid. See there for docs.
-  aos_mutex tid;
+  RobustOwnershipTracker ownership_tracker;
 
   // Queue index of the message we have pinned, or Invalid if there isn't one.
   AtomicQueueIndex pinned;
@@ -200,6 +201,10 @@
 
 const static unsigned int kWakeupSignal = SIGRTMIN + 2;
 
+// Sets FUTEX_OWNER_DIED if the owner was tid.  This fakes what the kernel does
+// with a robust mutex.
+bool PretendThatOwnerIsDeadForTesting(aos_mutex *mutex, pid_t tid);
+
 // A convenient wrapper for accessing a lockless queue.
 class LocklessQueue {
  public:
@@ -268,7 +273,7 @@
   // up.  This isn't a copy of Watcher since tid is simpler to work with here
   // than the futex above.
   struct WatcherCopy {
-    pid_t tid;
+    ThreadOwnerStatusSnapshot ownership_snapshot;
     pid_t pid;
     int priority;
   };
diff --git a/aos/ipc_lib/lockless_queue_death_test.cc b/aos/ipc_lib/lockless_queue_death_test.cc
index 2a95b31..2f4e08a 100644
--- a/aos/ipc_lib/lockless_queue_death_test.cc
+++ b/aos/ipc_lib/lockless_queue_death_test.cc
@@ -104,10 +104,12 @@
         // TestShmRobustness doesn't handle robust futexes.  It is happy to just
         // not crash with them.  We know what they are, and what the tid of the
         // holder is.  So go pretend to be the kernel and fix it for it.
-        PretendOwnerDied(&memory->queue_setup_lock, tid.Get());
+        PretendThatOwnerIsDeadForTesting(&memory->queue_setup_lock, tid.Get());
 
         for (size_t i = 0; i < config.num_senders; ++i) {
-          if (PretendOwnerDied(&memory->GetSender(i)->tid, tid.Get())) {
+          if (memory->GetSender(i)
+                  ->ownership_tracker.PretendThatOwnerIsDeadForTesting(
+                      tid.Get())) {
             // Print out before and after results if a sender died.  That is the
             // more fun case.
             print = true;
diff --git a/aos/ipc_lib/lockless_queue_stepping.cc b/aos/ipc_lib/lockless_queue_stepping.cc
index 0ec5214..e819795 100644
--- a/aos/ipc_lib/lockless_queue_stepping.cc
+++ b/aos/ipc_lib/lockless_queue_stepping.cc
@@ -458,14 +458,6 @@
 
 pid_t SharedTid::Get() { return *tid_; }
 
-bool PretendOwnerDied(aos_mutex *mutex, pid_t tid) {
-  if ((mutex->futex & FUTEX_TID_MASK) == tid) {
-    mutex->futex = FUTEX_OWNER_DIED;
-    return true;
-  }
-  return false;
-}
-
 }  // namespace testing
 }  // namespace ipc_lib
 }  // namespace aos
diff --git a/aos/ipc_lib/lockless_queue_stepping.h b/aos/ipc_lib/lockless_queue_stepping.h
index b3eb6e3..7aab193 100644
--- a/aos/ipc_lib/lockless_queue_stepping.h
+++ b/aos/ipc_lib/lockless_queue_stepping.h
@@ -140,10 +140,6 @@
   pid_t *tid_;
 };
 
-// Sets FUTEX_OWNER_DIED if the owner was tid.  This fakes what the kernel does
-// with a robust mutex.
-bool PretendOwnerDied(aos_mutex *mutex, pid_t tid);
-
 #endif
 
 }  // namespace testing
diff --git a/aos/ipc_lib/lockless_queue_test.cc b/aos/ipc_lib/lockless_queue_test.cc
index 58e093f..14701e2 100644
--- a/aos/ipc_lib/lockless_queue_test.cc
+++ b/aos/ipc_lib/lockless_queue_test.cc
@@ -538,7 +538,7 @@
         ::aos::ipc_lib::LocklessQueueMemory *const memory =
             reinterpret_cast<::aos::ipc_lib::LocklessQueueMemory *>(raw_memory);
         LocklessQueue queue(memory, memory, config);
-        PretendOwnerDied(&memory->queue_setup_lock, tid.Get());
+        PretendThatOwnerIsDeadForTesting(&memory->queue_setup_lock, tid.Get());
 
         if (VLOG_IS_ON(1)) {
           PrintLocklessQueueMemory(memory);
diff --git a/aos/ipc_lib/robust_ownership_tracker.cc b/aos/ipc_lib/robust_ownership_tracker.cc
new file mode 100644
index 0000000..3b6d32a
--- /dev/null
+++ b/aos/ipc_lib/robust_ownership_tracker.cc
@@ -0,0 +1,29 @@
+#include "aos/ipc_lib/robust_ownership_tracker.h"
+
+#include "aos/ipc_lib/lockless_queue.h"
+
+namespace aos {
+namespace ipc_lib {
+
+bool RobustOwnershipTracker::PretendThatOwnerIsDeadForTesting(pid_t died_tid) {
+  return ipc_lib::PretendThatOwnerIsDeadForTesting(&mutex_, died_tid);
+}
+
+::std::string RobustOwnershipTracker::DebugString() const {
+  ::std::stringstream s;
+  s << "{.tid=aos_mutex(" << ::std::hex << mutex_.futex;
+
+  if (mutex_.futex != 0) {
+    s << ":";
+    if (mutex_.futex & FUTEX_OWNER_DIED) {
+      s << "FUTEX_OWNER_DIED|";
+    }
+    s << "tid=" << (mutex_.futex & FUTEX_TID_MASK);
+  }
+
+  s << "),}";
+  return s.str();
+}
+
+}  // namespace ipc_lib
+}  // namespace aos
diff --git a/aos/ipc_lib/robust_ownership_tracker.h b/aos/ipc_lib/robust_ownership_tracker.h
new file mode 100644
index 0000000..968c2f7
--- /dev/null
+++ b/aos/ipc_lib/robust_ownership_tracker.h
@@ -0,0 +1,125 @@
+#ifndef AOS_IPC_LIB_ROBUST_OWNERSHIP_TRACKER_H_
+#define AOS_IPC_LIB_ROBUST_OWNERSHIP_TRACKER_H_
+
+#include <linux/futex.h>
+
+#include <string>
+
+#include "aos/ipc_lib/aos_sync.h"
+
+namespace aos::ipc_lib {
+
+// Results of atomically loading the ownership state via RobustOwnershipTracker
+// below. This allows the state to be compared and queried later.
+class ThreadOwnerStatusSnapshot {
+ public:
+  ThreadOwnerStatusSnapshot() : futex_(0) {}
+  ThreadOwnerStatusSnapshot(aos_futex futex) : futex_(futex) {}
+  ThreadOwnerStatusSnapshot(const ThreadOwnerStatusSnapshot &) = default;
+  ThreadOwnerStatusSnapshot &operator=(const ThreadOwnerStatusSnapshot &) =
+      default;
+  ThreadOwnerStatusSnapshot(ThreadOwnerStatusSnapshot &&) = default;
+  ThreadOwnerStatusSnapshot &operator=(ThreadOwnerStatusSnapshot &&) = default;
+
+  // Returns if the owner died as noticed by the robust futex using Acquire
+  // memory ordering.
+  bool OwnerIsDead() const { return (futex_ & FUTEX_OWNER_DIED) != 0; }
+
+  // Returns true if no one has claimed ownership.
+  bool IsUnclaimed() const { return futex_ == 0; }
+
+  // Returns true if either ownership hasn't been acquired or the owner died.
+  bool IsUnclaimedOrOwnerIsDead() const {
+    return IsUnclaimed() || OwnerIsDead();
+  }
+
+  // Returns the thread ID (a.k.a. "tid") of the owning thread. Use this when
+  // trying to access the /proc entry that corresponds to the owning thread for
+  // example. Do not use the futex value directly.
+  pid_t tid() const { return futex_ & FUTEX_TID_MASK; }
+
+  bool operator==(const ThreadOwnerStatusSnapshot &other) const {
+    return other.futex_ == futex_;
+  }
+
+ private:
+  aos_futex futex_;
+};
+
+// This object reliably tracks a thread owning a resource. A single thread may
+// possess multiple resources like senders and receivers. Each resource can have
+// its own instance of this class. These instances are responsible for
+// monitoring the thread that owns them. Each resource can use its instance of
+// this class to reliably check whether the owning thread is no longer alive.
+//
+// All methods other than Load* must be accessed under a mutex.
+class RobustOwnershipTracker {
+ public:
+  // Loads all the contents of the ownership tracker with Acquire memory
+  // ordering.
+  ThreadOwnerStatusSnapshot LoadAcquire() const {
+    return ThreadOwnerStatusSnapshot(
+        __atomic_load_n(&(mutex_.futex), __ATOMIC_ACQUIRE));
+  }
+
+  // Loads all the contents of the ownership tracker with Relaxed memory order.
+  ThreadOwnerStatusSnapshot LoadRelaxed() const {
+    return ThreadOwnerStatusSnapshot(
+        __atomic_load_n(&(mutex_.futex), __ATOMIC_RELAXED));
+  }
+
+  // Clears all ownership state.
+  //
+  // This should only really be called if you are 100% certain that the owner is
+  // dead. Use `LoadAquire().OwnerIsDead()` to determine this.
+  void ForceClear() {
+    // Must be opposite order of Acquire.
+    // We only deal with the futex here because we don't want to change anything
+    // about the linked list. We just want to release ownership here. We still
+    // want the kernel to know about this element via the linked list the next
+    // time someone takes ownership.
+    __atomic_store_n(&(mutex_.futex), 0, __ATOMIC_RELEASE);
+  }
+
+  // Returns true if this thread holds ownership.
+  bool IsHeldBySelf() { return death_notification_is_held(&mutex_); }
+
+  // Acquires ownership. Other threads will know that this thread holds the
+  // ownership or be notified if this thread dies.
+  void Acquire() { death_notification_init(&mutex_); }
+
+  // Releases ownership.
+  //
+  // This should only be called from the owning thread.
+  void Release() {
+    // Must be opposite order of Acquire.
+    death_notification_release(&mutex_);
+  }
+
+  // Marks the owner as dead if the specified tid is the current owner. In other
+  // words, after this call, a call to `LoadAcquire().OwnerIsDead()` may start
+  // returning true.
+  //
+  // The motivation here is for use in testing. DO NOT USE in production code.
+  // The logic here is only good enough for testing.
+  bool PretendThatOwnerIsDeadForTesting(pid_t tid);
+
+  // Returns a string representing this object.
+  std::string DebugString() const;
+
+ private:
+  // Robust futex to track ownership the normal way. The futex is inside the
+  // mutex here. We use the wrapper mutex because the death_notification_*
+  // functions operate on that instead of the futex directly.
+  //
+  // We use a futex here because:
+  // - futexes are fast.
+  // - The kernel can atomically clean up a dead thread and mark the futex
+  //   appropriately.
+  // - Owners can clean up after dead threads.
+  aos_mutex mutex_;
+};
+
+}  // namespace aos::ipc_lib
+
+#endif  // AOS_IPC_LIB_ROBUST_OWNERSHIP_TRACKER_H_