add an implementation of python-style events

This is another synchronization primitive. It will be used in a couple
of extra tests coming right after this.

Change-Id: I536f04f0b5f4f87a0c0a4c8e531c526c7a56bb49
diff --git a/aos/build/aos_all.gyp b/aos/build/aos_all.gyp
index bd333a3..4d28c5c 100644
--- a/aos/build/aos_all.gyp
+++ b/aos/build/aos_all.gyp
@@ -34,6 +34,7 @@
         '<(AOS)/common/common.gyp:mutex_test',
         '<(AOS)/common/common.gyp:condition_test',
         '<(AOS)/common/common.gyp:once_test',
+        '<(AOS)/common/common.gyp:event_test',
         '<(AOS)/common/logging/logging.gyp:logging_impl_test',
         '<(AOS)/common/util/util.gyp:options_test',
         '<(AOS)/common/common.gyp:queue_test',
diff --git a/aos/common/common.gyp b/aos/common/common.gyp
index a0750fe..f5db470 100644
--- a/aos/common/common.gyp
+++ b/aos/common/common.gyp
@@ -252,6 +252,20 @@
       ],
     },
     {
+      'target_name': 'event',
+      'type': 'static_library',
+      'sources': [
+        '<(AOS)/linux_code/ipc_lib/event.cc',
+      ],
+      'dependencies': [
+        '<(AOS)/linux_code/ipc_lib/ipc_lib.gyp:aos_sync',
+        '<(AOS)/build/aos.gyp:logging_interface',
+      ],
+      'export_dependent_settings': [
+        '<(AOS)/linux_code/ipc_lib/ipc_lib.gyp:aos_sync',
+      ],
+    },
+    {
       'target_name': 'mutex_test',
       'type': 'executable',
       'sources': [
@@ -269,6 +283,19 @@
       ],
     },
     {
+      'target_name': 'event_test',
+      'type': 'executable',
+      'sources': [
+        'event_test.cc',
+      ],
+      'dependencies': [
+        '<(EXTERNALS):gtest',
+        'event',
+        'queue_testutils',
+        'time',
+      ],
+    },
+    {
       'target_name': 'condition_test',
       'type': 'executable',
       'sources': [
diff --git a/aos/common/event.h b/aos/common/event.h
new file mode 100644
index 0000000..9839d41
--- /dev/null
+++ b/aos/common/event.h
@@ -0,0 +1,53 @@
+#ifndef AOS_COMMON_EVENT_H_
+#define AOS_COMMON_EVENT_H_
+
+#include "aos/linux_code/ipc_lib/aos_sync.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();
+
+  // 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_COMMON_EVENT_H_
diff --git a/aos/common/event_test.cc b/aos/common/event_test.cc
new file mode 100644
index 0000000..fff7522
--- /dev/null
+++ b/aos/common/event_test.cc
@@ -0,0 +1,66 @@
+#include "aos/common/event.h"
+
+#include <thread>
+
+#include "gtest/gtest.h"
+
+#include "aos/common/queue_testutils.h"
+#include "aos/common/time.h"
+
+namespace aos {
+namespace testing {
+
+class EventTest : public ::testing::Test {
+ public:
+  Event test_event;
+
+ protected:
+  void SetUp() override {
+    ::aos::common::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 < 1000; ++i) {
+    int variable = 0;
+    ::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) {
+  time::Time start_time, finish_time;
+  ::std::thread thread([this, &start_time, &finish_time]() {
+    start_time = time::Time::Now();
+    test_event.Wait();
+    finish_time = time::Time::Now();
+  });
+  static const time::Time kWaitTime = time::Time::InSeconds(0.05);
+  time::SleepFor(kWaitTime);
+  test_event.Set();
+  thread.join();
+  EXPECT_GE(finish_time - start_time, kWaitTime);
+}
+
+}  // namespace testing
+}  // namespace aos
diff --git a/aos/linux_code/ipc_lib/event.cc b/aos/linux_code/ipc_lib/event.cc
new file mode 100644
index 0000000..e95b45e
--- /dev/null
+++ b/aos/linux_code/ipc_lib/event.cc
@@ -0,0 +1,35 @@
+#include "aos/common/event.h"
+
+#include "aos/common/type_traits.h"
+#include "aos/common/logging/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() {
+  int ret;
+  do {
+    ret = futex_wait(&impl_);
+  } while (ret == 1);
+  if (ret == 0) return;
+  CHECK_EQ(-1, ret);
+  PLOG(FATAL, "futex_wait(%p) failed", &impl_);
+}
+
+// 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(%p) failed", &impl_);
+  }
+}
+
+bool Event::Clear() {
+  return !futex_unset(&impl_);
+}
+
+}  // namespace aos