Removed Common
Change-Id: I01ea8f07220375c2ad9bc0092281d4f27c642303
diff --git a/aos/transaction/BUILD b/aos/transaction/BUILD
new file mode 100644
index 0000000..3d5a29f
--- /dev/null
+++ b/aos/transaction/BUILD
@@ -0,0 +1,26 @@
+package(default_visibility = ["//visibility:public"])
+
+cc_library(
+ name = "transaction",
+ hdrs = [
+ "transaction.h",
+ ],
+ deps = [
+ "//aos/logging",
+ "//aos/util:compiler_memory_barrier",
+ ],
+)
+
+cc_test(
+ name = "transaction_test",
+ srcs = [
+ "transaction_test.cc",
+ ],
+ deps = [
+ ":transaction",
+ "//aos/logging",
+ "//aos/util:death_test_log_implementation",
+ "//aos/testing:googletest",
+ ],
+)
+
diff --git a/aos/transaction/transaction.h b/aos/transaction/transaction.h
new file mode 100644
index 0000000..f1c5c02
--- /dev/null
+++ b/aos/transaction/transaction.h
@@ -0,0 +1,103 @@
+#ifndef AOS_TRANSACTION_H_
+#define AOS_TRANSACTION_H_
+
+#include <stdint.h>
+
+#include <array>
+
+#include "aos/util/compiler_memory_barrier.h"
+#include "aos/logging/logging.h"
+
+namespace aos {
+namespace transaction {
+
+// Manages a LIFO stack of Work objects. Designed to help implement transactions
+// by providing a safe way to undo things etc.
+//
+// number_works Work objects are created statically and then Create is called on
+// each as it is added to the stack. When the work should do whatever it does,
+// DoWork() will be called. The work objects get no notification when they are
+// dropped off of the stack.
+//
+// Work::DoWork() must be idempotent because it may get called multiple times if
+// CompleteWork() is interrupted part of the way through.
+//
+// This class handles compiler memory barriers etc to make sure only fully
+// created works are ever invoked, and each work will be fully created by the
+// time AddWork returns. This does not mean it's safe for multiple threads to
+// interact with an instance of this class at the same time.
+template <class Work, int number_works>
+class WorkStack {
+ public:
+ // Calls DoWork() on all the works that have been added and then removes them
+ // all from the stack.
+ void CompleteWork() {
+ int current = stack_index_;
+ while (current > 0) {
+ stack_.at(--current).DoWork();
+ }
+ aos_compiler_memory_barrier();
+ stack_index_ = 0;
+ aos_compiler_memory_barrier();
+ }
+
+ // Drops all works that have been added.
+ void DropWork() {
+ stack_index_ = 0;
+ aos_compiler_memory_barrier();
+ }
+
+ // Returns true if we have any works to complete right now.
+ bool HasWork() const { return stack_index_ != 0; }
+
+ // Forwards all of its arguments to Work::Create, which it calls on the next
+ // work to be added.
+ template <class... A>
+ void AddWork(A &&... a) {
+ if (stack_index_ >= number_works) {
+ LOG(FATAL, "too many works\n");
+ }
+ stack_.at(stack_index_).Create(::std::forward<A>(a)...);
+ aos_compiler_memory_barrier();
+ ++stack_index_;
+ aos_compiler_memory_barrier();
+ }
+
+ private:
+ // The next index into stack_ for a new work to be added.
+ int stack_index_ = 0;
+ ::std::array<Work, number_works> stack_;
+};
+
+// When invoked, sets *pointer to the value it had when this work was Created.
+template <class T>
+class RestoreValueWork {
+ public:
+ void Create(T *pointer) {
+ pointer_ = pointer;
+ value_ = *pointer;
+ }
+ void DoWork() {
+ *pointer_ = value_;
+ }
+
+ private:
+ T *pointer_;
+ T value_;
+};
+
+// Handles the casting necessary to restore any kind of pointer.
+class RestorePointerWork : public RestoreValueWork<void *> {
+ public:
+ template <class T>
+ void Create(T **pointer) {
+ static_assert(sizeof(T *) == sizeof(void *),
+ "that's a weird pointer");
+ RestoreValueWork<void *>::Create(reinterpret_cast<void **>(pointer));
+ }
+};
+
+} // namespace transaction
+} // namespace aos
+
+#endif // AOS_TRANSACTION_H_
diff --git a/aos/transaction/transaction_test.cc b/aos/transaction/transaction_test.cc
new file mode 100644
index 0000000..f7551c8
--- /dev/null
+++ b/aos/transaction/transaction_test.cc
@@ -0,0 +1,106 @@
+#include "aos/transaction/transaction.h"
+
+#include <vector>
+
+#include "gtest/gtest.h"
+
+#include "aos/util/death_test_log_implementation.h"
+
+namespace aos {
+namespace transaction {
+namespace testing {
+
+class WorkStackTest : public ::testing::Test {
+ public:
+ // Contains an index which it adds to the created_works and invoked_works
+ // vectors of its containing WorkStackTest.
+ class TestWork {
+ public:
+ void Create(WorkStackTest *test, int i) {
+ test->created_works()->push_back(i);
+ i_ = i;
+ test_ = test;
+ }
+ void DoWork() {
+ test_->invoked_works()->push_back(i_);
+ }
+
+ int i() const { return i_; }
+
+ private:
+ int i_;
+ WorkStackTest *test_;
+ };
+
+ ::std::vector<int> *created_works() { return &created_works_; }
+ ::std::vector<int> *invoked_works() { return &invoked_works_; }
+ WorkStack<TestWork, 20> *work_stack() { return &work_stack_; }
+
+ // Creates a TestWork with index i and adds it to work_stack().
+ void CreateWork(int i) {
+ work_stack_.AddWork(this, i);
+ }
+
+ private:
+ ::std::vector<int> created_works_, invoked_works_;
+ WorkStack<TestWork, 20> work_stack_;
+};
+
+typedef WorkStackTest WorkStackDeathTest;
+
+TEST_F(WorkStackTest, Basic) {
+ EXPECT_FALSE(work_stack()->HasWork());
+ EXPECT_EQ(0u, created_works()->size());
+ EXPECT_EQ(0u, invoked_works()->size());
+
+ CreateWork(971);
+ EXPECT_TRUE(work_stack()->HasWork());
+ EXPECT_EQ(1u, created_works()->size());
+ EXPECT_EQ(0u, invoked_works()->size());
+ EXPECT_EQ(971, created_works()->at(0));
+
+ work_stack()->CompleteWork();
+ EXPECT_FALSE(work_stack()->HasWork());
+ EXPECT_EQ(1u, created_works()->size());
+ EXPECT_EQ(1u, invoked_works()->size());
+ EXPECT_EQ(971, invoked_works()->at(0));
+}
+
+TEST_F(WorkStackTest, DropWork) {
+ CreateWork(971);
+ CreateWork(254);
+ EXPECT_EQ(2u, created_works()->size());
+
+ work_stack()->DropWork();
+ EXPECT_FALSE(work_stack()->HasWork());
+ work_stack()->CompleteWork();
+ EXPECT_EQ(0u, invoked_works()->size());
+}
+
+// Tests that the works get run in the correct order.
+TEST_F(WorkStackTest, InvocationOrder) {
+ CreateWork(971);
+ CreateWork(254);
+ CreateWork(1678);
+
+ work_stack()->CompleteWork();
+ EXPECT_EQ((::std::vector<int>{971, 254, 1678}), *created_works());
+ EXPECT_EQ((::std::vector<int>{1678, 254, 971}), *invoked_works());
+}
+
+// Tests that it handles adding too many works intelligently.
+TEST_F(WorkStackDeathTest, TooManyWorks) {
+ logging::Init();
+ EXPECT_DEATH(
+ {
+ logging::AddImplementation(new util::DeathTestLogImplementation());
+ for (int i = 0; i < 1000; ++i) {
+ CreateWork(i);
+ }
+ },
+ ".*too many works.*");
+}
+
+} // namespace testing
+} // namespace transaction
+} // namespace aos