Move event.h into ipc_lib

Makes tab completion a lot less annoying.

Change-Id: I0c905ba112c274d23b5f0277b9a5c2a8e4e238f6
diff --git a/aos/ipc_lib/BUILD b/aos/ipc_lib/BUILD
index 7799cdc..026f866 100644
--- a/aos/ipc_lib/BUILD
+++ b/aos/ipc_lib/BUILD
@@ -100,8 +100,8 @@
     linkopts = ["-lrt"],
     target_compatible_with = ["@platforms//os:linux"],
     deps = [
+        ":event",
         "//aos:condition",
-        "//aos:event",
         "//aos:init",
         "//aos/logging",
         "//aos/logging:implementations",
@@ -194,8 +194,8 @@
     ],
     target_compatible_with = ["@platforms//os:linux"],
     deps = [
+        ":event",
         ":lockless_queue",
-        "//aos:event",
         "//aos/testing:googletest",
     ],
 )
@@ -206,10 +206,10 @@
     srcs = ["lockless_queue_test.cc"],
     target_compatible_with = ["@platforms//os:linux"],
     deps = [
+        ":event",
         ":lockless_queue",
         ":queue_racer",
         ":signalfd",
-        "//aos:event",
         "//aos/events:epoll",
         "//aos/testing:googletest",
         "//aos/testing:prevent_exit",
@@ -221,10 +221,10 @@
     srcs = ["lockless_queue_death_test.cc"],
     target_compatible_with = ["@platforms//os:linux"],
     deps = [
+        ":event",
         ":lockless_queue",
         ":queue_racer",
         ":signalfd",
-        "//aos:event",
         "//aos/events:epoll",
         "//aos/libc:aos_strsignal",
         "//aos/testing:googletest",
@@ -326,3 +326,35 @@
         ":lockless_queue",
     ],
 )
+
+cc_library(
+    name = "event",
+    srcs = [
+        "event.cc",
+    ],
+    hdrs = [
+        "event.h",
+    ],
+    target_compatible_with = ["@platforms//os:linux"],
+    visibility = ["//visibility:public"],
+    deps = [
+        "//aos/ipc_lib:aos_sync",
+        "//aos/time",
+        "//aos/type_traits",
+        "@com_github_google_glog//:glog",
+    ],
+)
+
+cc_test(
+    name = "event_test",
+    srcs = [
+        "event_test.cc",
+    ],
+    target_compatible_with = ["@platforms//os:linux"],
+    deps = [
+        ":event",
+        "//aos/testing:googletest",
+        "//aos/testing:test_logging",
+        "//aos/time",
+    ],
+)
diff --git a/aos/ipc_lib/event.cc b/aos/ipc_lib/event.cc
new file mode 100644
index 0000000..940dfbd
--- /dev/null
+++ b/aos/ipc_lib/event.cc
@@ -0,0 +1,56 @@
+#include "aos/ipc_lib/event.h"
+
+#include <chrono>
+
+#include "aos/type_traits/type_traits.h"
+#include "glog/logging.h"
+
+namespace aos {
+
+Event::Event() : impl_(0) {
+  static_assert(shm_ok<Event>::value,
+                "Event is not safe for use in shared memory.");
+}
+
+void Event::Wait() {
+  while (__atomic_load_n(&impl_, __ATOMIC_SEQ_CST) == 0) {
+    const int ret = futex_wait(&impl_);
+    if (ret != 0) {
+      CHECK_EQ(-1, ret);
+      PLOG(FATAL) << "futex_wait(" << &impl_ << ") failed";
+    }
+  }
+}
+
+bool Event::WaitTimeout(monotonic_clock::duration timeout) {
+  ::std::chrono::seconds sec =
+      ::std::chrono::duration_cast<::std::chrono::seconds>(timeout);
+  ::std::chrono::nanoseconds nsec =
+      ::std::chrono::duration_cast<::std::chrono::nanoseconds>(timeout - sec);
+  struct timespec timeout_timespec;
+  timeout_timespec.tv_sec = sec.count();
+  timeout_timespec.tv_nsec = nsec.count();
+  while (true) {
+    if (__atomic_load_n(&impl_, __ATOMIC_SEQ_CST) != 0) {
+      return true;
+    }
+    const int ret = futex_wait_timeout(&impl_, &timeout_timespec);
+    if (ret != 0) {
+      if (ret == 2) return false;
+      CHECK_EQ(-1, ret);
+      PLOG(FATAL) << "futex_wait(" << &impl_ << ") failed";
+    }
+  }
+}
+
+// We're not going to expose the number woken because that's not easily portable
+// to condition variable-based implementations.
+void Event::Set() {
+  if (futex_set(&impl_) == -1) {
+    PLOG(FATAL) << "futex_set(" << &impl_ << ") failed";
+  }
+}
+
+bool Event::Clear() { return !futex_unset(&impl_); }
+
+}  // namespace aos
diff --git a/aos/ipc_lib/event.h b/aos/ipc_lib/event.h
new file mode 100644
index 0000000..d8440e8
--- /dev/null
+++ b/aos/ipc_lib/event.h
@@ -0,0 +1,59 @@
+#ifndef AOS_IPC_LIB_EVENT_H_
+#define AOS_IPC_LIB_EVENT_H_
+
+#include "aos/ipc_lib/aos_sync.h"
+#include "aos/time/time.h"
+
+namespace aos {
+
+// An abstraction of an event which is easy to implement for Linux and in other
+// environments.
+// On Linux at least, this is definitely safe for passing through C code with
+// memcpy etc.
+//
+// An event is either "set" or "unset". Any thread can transition it between
+// these two states and other threads can wait for an unset->set transition.
+// This is not a particularly powerful synchronization primitive, but it can be
+// much easier to use than more complicated ones in some situations. The name is
+// taken from Python's implementation of the same thing.
+//
+// An event is equivalent to a semaphore which is either set to 0 or infinity.
+// It is also equivalent to a condition variable with the monitored condition
+// being "is it set or not".
+//
+// IMPORTANT: You can NOT use this to successfully replace a standard condition
+// variable in most cases. When the condition being monitored changes separately
+// from the actual state of the condition variable/event, there WILL be race
+// conditions if you try to use this class.
+//
+// It is undefined behavior to destroy an Event while there are current
+// Wait()ers.
+class Event {
+ public:
+  // Creates an unset event.
+  Event();
+  // There must be no waiters when an Event is destroyed.
+  ~Event() = default;
+
+  // Waits for the event to be set. Returns immediately if it is already set.
+  void Wait();
+
+  // Waits for the event to be set or until timeout has elapsed. Returns
+  // immediately if it is already set.
+  // Returns true if the event was Set or false if the timeout expired.
+  bool WaitTimeout(monotonic_clock::duration timeout);
+
+  // Wakes up all Wait()ers and sets the event (atomically).
+  void Set();
+  // Unsets the event so future Wait() callers will block instead of returning
+  // immediately.
+  // Returns true if the event was previously set.
+  bool Clear();
+
+ private:
+  aos_futex impl_;
+};
+
+}  // namespace aos
+
+#endif  // AOS_IPC_LIB_EVENT_H_
diff --git a/aos/ipc_lib/event_test.cc b/aos/ipc_lib/event_test.cc
new file mode 100644
index 0000000..fb2421e
--- /dev/null
+++ b/aos/ipc_lib/event_test.cc
@@ -0,0 +1,92 @@
+#include "aos/ipc_lib/event.h"
+
+#include <chrono>
+#include <thread>
+
+#include "aos/testing/test_logging.h"
+#include "aos/time/time.h"
+#include "gtest/gtest.h"
+
+namespace aos {
+namespace testing {
+
+namespace chrono = ::std::chrono;
+namespace this_thread = ::std::this_thread;
+
+class EventTest : public ::testing::Test {
+ public:
+  Event test_event_;
+
+ protected:
+  void SetUp() override { ::aos::testing::EnableTestLogging(); }
+};
+
+// Makes sure that basic operations with no blocking or anything work.
+TEST_F(EventTest, Basic) {
+  EXPECT_FALSE(test_event_.Clear());
+  EXPECT_FALSE(test_event_.Clear());
+
+  test_event_.Set();
+  test_event_.Wait();
+  EXPECT_TRUE(test_event_.Clear());
+  EXPECT_FALSE(test_event_.Clear());
+}
+
+// Tests that tsan understands that events establish a happens-before
+// relationship.
+TEST_F(EventTest, ThreadSanitizer) {
+  for (int i = 0; i < 3000; ++i) {
+    int variable = 0;
+    test_event_.Clear();
+    ::std::thread thread([this, &variable]() {
+      test_event_.Wait();
+      --variable;
+    });
+    ++variable;
+    test_event_.Set();
+    thread.join();
+    EXPECT_EQ(0, variable);
+  }
+}
+
+// Tests that an event blocks correctly.
+TEST_F(EventTest, Blocks) {
+  monotonic_clock::time_point start_time, finish_time;
+  // Without this, it sometimes manages to fail under tsan.
+  Event started;
+  ::std::thread thread([this, &start_time, &finish_time, &started]() {
+    start_time = monotonic_clock::now();
+    started.Set();
+    test_event_.Wait();
+    finish_time = monotonic_clock::now();
+  });
+  static constexpr auto kWaitTime = chrono::milliseconds(50);
+  started.Wait();
+  this_thread::sleep_for(kWaitTime);
+  test_event_.Set();
+  thread.join();
+  EXPECT_GE(finish_time - start_time, kWaitTime);
+}
+
+TEST_F(EventTest, WaitTimeout) {
+  EXPECT_FALSE(test_event_.WaitTimeout(chrono::milliseconds(50)));
+
+  monotonic_clock::time_point start_time, finish_time;
+  // Without this, it sometimes manages to fail under tsan.
+  Event started;
+  ::std::thread thread([this, &start_time, &finish_time, &started]() {
+    start_time = monotonic_clock::now();
+    started.Set();
+    EXPECT_TRUE(test_event_.WaitTimeout(chrono::milliseconds(500)));
+    finish_time = monotonic_clock::now();
+  });
+  constexpr auto kWaitTime = chrono::milliseconds(50);
+  started.Wait();
+  this_thread::sleep_for(kWaitTime);
+  test_event_.Set();
+  thread.join();
+  EXPECT_GE(finish_time - start_time, kWaitTime);
+}
+
+}  // namespace testing
+}  // namespace aos
diff --git a/aos/ipc_lib/ipc_comparison.cc b/aos/ipc_lib/ipc_comparison.cc
index cf19b20..af1098d 100644
--- a/aos/ipc_lib/ipc_comparison.cc
+++ b/aos/ipc_lib/ipc_comparison.cc
@@ -1,5 +1,3 @@
-#include "gflags/gflags.h"
-
 #include <fcntl.h>
 #include <mqueue.h>
 #include <netinet/in.h>
@@ -22,13 +20,14 @@
 #include <thread>
 
 #include "aos/condition.h"
-#include "aos/event.h"
 #include "aos/init.h"
+#include "aos/ipc_lib/event.h"
 #include "aos/logging/implementations.h"
 #include "aos/logging/logging.h"
 #include "aos/mutex/mutex.h"
 #include "aos/realtime.h"
 #include "aos/time/time.h"
+#include "gflags/gflags.h"
 
 DEFINE_string(method, "", "Which IPC method to use");
 DEFINE_int32(messages, 1000000, "How many messages to send back and forth");
diff --git a/aos/ipc_lib/lockless_queue_test.cc b/aos/ipc_lib/lockless_queue_test.cc
index 6510501..a90b976 100644
--- a/aos/ipc_lib/lockless_queue_test.cc
+++ b/aos/ipc_lib/lockless_queue_test.cc
@@ -4,14 +4,15 @@
 #include <signal.h>
 #include <unistd.h>
 #include <wait.h>
+
 #include <chrono>
 #include <memory>
 #include <random>
 #include <thread>
 
-#include "aos/event.h"
 #include "aos/events/epoll.h"
 #include "aos/ipc_lib/aos_sync.h"
+#include "aos/ipc_lib/event.h"
 #include "aos/ipc_lib/queue_racer.h"
 #include "aos/ipc_lib/signalfd.h"
 #include "aos/realtime.h"
@@ -127,8 +128,7 @@
                            alignof(LocklessQueueWatcher)>::type data;
     new (&data)
         LocklessQueueWatcher(LocklessQueueWatcher::Make(queue(), 5).value());
-  })
-      .join();
+  }).join();
 
   EXPECT_EQ(wake_upper.Wakeup(7), 0);
 }
diff --git a/aos/ipc_lib/queue_racer.cc b/aos/ipc_lib/queue_racer.cc
index fcc8668..5b3d88b 100644
--- a/aos/ipc_lib/queue_racer.cc
+++ b/aos/ipc_lib/queue_racer.cc
@@ -2,9 +2,10 @@
 
 #include <inttypes.h>
 #include <string.h>
+
 #include <limits>
 
-#include "aos/event.h"
+#include "aos/ipc_lib/event.h"
 #include "gtest/gtest.h"
 
 namespace aos {