Use dedicated functions for using futexes to wait for a task to die

This avoids hard-to-debug failure modes where processes hang in places
they should never block in the new queue code.

Change-Id: I1f1749a608b4f81990685ff718908f8b68a2262a
diff --git a/aos/ipc_lib/aos_sync.cc b/aos/ipc_lib/aos_sync.cc
index efb1fb1..d5c3d4e 100644
--- a/aos/ipc_lib/aos_sync.cc
+++ b/aos/ipc_lib/aos_sync.cc
@@ -851,6 +851,44 @@
   return (value & FUTEX_TID_MASK) == tid;
 }
 
+void death_notification_init(aos_mutex *m) {
+  const uint32_t tid = get_tid();
+  if (kPrintOperations) {
+    printf("%" PRId32 ": %p death_notification start\n", tid, m);
+  }
+  my_robust_list::Adder adder(m);
+  {
+    RunObservers run_observers(m, true);
+    CHECK(compare_and_swap(&m->futex, 0, tid));
+  }
+  adder.Add();
+}
+
+void death_notification_release(aos_mutex *m) {
+  RunObservers run_observers(m, true);
+
+#ifndef NDEBUG
+  // Verify it's "locked", like it should be.
+  {
+    const uint32_t tid = get_tid();
+    if (kPrintOperations) {
+      printf("%" PRId32 ": %p death_notification release\n", tid, m);
+    }
+    const uint32_t value = __atomic_load_n(&m->futex, __ATOMIC_SEQ_CST);
+    assert((value & ~FUTEX_WAITERS) == tid);
+  }
+#endif
+
+  my_robust_list::Remover remover(m);
+  ANNOTATE_HAPPENS_BEFORE(m);
+  const int ret = sys_futex_unlock_pi(&m->futex);
+  if (ret != 0) {
+    my_robust_list::robust_head.pending_next = 0;
+    errno = -ret;
+    PLOG(FATAL)  << "FUTEX_UNLOCK_PI(" << &m->futex << ") failed";
+  }
+}
+
 int condition_wait(aos_condition *c, aos_mutex *m, struct timespec *end_time) {
   RunObservers run_observers(c, false);
   const uint32_t tid = get_tid();
diff --git a/aos/ipc_lib/aos_sync.h b/aos/ipc_lib/aos_sync.h
index 2824516..8290faa 100644
--- a/aos/ipc_lib/aos_sync.h
+++ b/aos/ipc_lib/aos_sync.h
@@ -26,7 +26,7 @@
 // No initialization is necessary.
 typedef aos_futex aos_condition;
 
-// For use with the mutex_ functions.
+// For use with the mutex_ or death_notification_ functions.
 // futex must be initialized to 0.
 // No initialization is necessary for next and previous.
 // Under ThreadSanitizer, pthread_mutex_init must be initialized to false.
@@ -88,6 +88,25 @@
 // checking mutexes as they are destroyed to catch problems with that early and
 // stack-based recursive mutex locking.
 bool mutex_islocked(const aos_mutex *m);
+
+// The death_notification_ functions are designed for one thread to wait for
+// another thread (possibly in a different process) to end. This can mean
+// exiting gracefully or dying at any point. They use a standard aos_mutex, but
+// this mutex may not be used with any of the mutex_ functions.
+
+// Initializes a variable which can be used to wait for this thread to die.
+// This can only be called once after initializing *m.
+void death_notification_init(aos_mutex *m);
+
+// Manually triggers a death notification for this thread.
+// This thread must have previously called death_notification_init(m).
+void death_notification_release(aos_mutex *m);
+
+// Returns whether or not m is held by the current thread.
+// This is mainly useful as a debug assertion.
+inline bool death_notification_is_held(aos_mutex *m) {
+  return mutex_islocked(m);
+}
 #endif
 
 // The futex_ functions are similar to the mutex_ ones but different.