From 3ca9acb90a45babf9e35541cb58eb90d4fbdfd3a Mon Sep 17 00:00:00 2001
From: florijan <florijan@memgraph.io>
Date: Tue, 3 Oct 2017 09:29:07 +0200
Subject: [PATCH] Copy-on-write added to POC

Reviewers: buda, mislav.bradac

Reviewed By: mislav.bradac

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D848
---
 poc/utils/copy_on_write.hpp       | 106 ++++++++++++++++++++++++++++++
 poc/utils/utils_copy_on_write.cpp |  67 +++++++++++++++++++
 2 files changed, 173 insertions(+)
 create mode 100644 poc/utils/copy_on_write.hpp
 create mode 100644 poc/utils/utils_copy_on_write.cpp

diff --git a/poc/utils/copy_on_write.hpp b/poc/utils/copy_on_write.hpp
new file mode 100644
index 000000000..3a3d4683d
--- /dev/null
+++ b/poc/utils/copy_on_write.hpp
@@ -0,0 +1,106 @@
+#pragma once
+
+#include <memory>
+
+#include "utils/assert.hpp"
+
+namespace utils {
+
+/**
+ * Helper class for implementing copy-on-write member variables. Memory
+ * management is automatic via shared-ptr: it is allowed for the original
+ * owner to expire, the content will remain valid in copies.
+ *
+ * This class is generally not thread-safe. However, it is intended for use in
+ * Memgraph's MVCC which is concurrent, but with specific guarantees that also
+ * make it possible to use this. These guarantees are:
+ *   1. Writing can only happen when there are no copies (no parallel reads). An
+ * implication of this is that an obtained copy is immutable, even though in
+ * general CopyOnWrite does not guarantee this (generally the owner's
+ * modificatins will be visible to the copies).
+ *   2. Copies are not created in parallel. MVCC guarantees this with
+ * record-locking, but should be in fact legal in CopyOnWrite.
+ *
+ * @tparam TElement - type of content. Must be copy-constructable.
+ */
+template <typename TElement>
+class CopyOnWrite {
+ public:
+  /**
+   * Creates a new CopyOnWrite that owns it's element.
+   *
+   * @param args - Arguments forwarded to the TElement constructor.
+   * @tparam TArgs - Argument types.
+   */
+  template <typename... TArgs>
+  CopyOnWrite(TArgs &&... args) {
+    TElement *new_element = new TElement(std::forward<TArgs>(args)...);
+    element_ptr_.reset(new_element);
+    is_owner_ = true;
+  }
+
+  /** Creates a copy of the given CopyOnWrite object that does not copy the
+   * element and does not assume ownership over it. */
+  CopyOnWrite(const CopyOnWrite &other)
+      : element_ptr_{other.element_ptr_}, is_owner_{false} {}
+
+  /** Creates a copy of the given CopyOnWrite object that does not copy the
+   * element and does not assume ownership over it. This is a non-const
+   * reference accepting copy constructor. The hack is necessary to prevent the
+   * variadic constructor from being a better match for non-const CopyOnWrite
+   * argument. */
+  CopyOnWrite(CopyOnWrite &other)
+      : CopyOnWrite(const_cast<const CopyOnWrite &>(other)) {}
+
+  /** Creates a copy of the given temporary CyopOnWrite object. If the temporary
+   * is owner then ownership is transferred to this CopyOnWrite. Otherwise this
+   * CopyOnWrite does not become the owner. */
+  CopyOnWrite(CopyOnWrite &&other) = default;
+
+  /** Copy assignment of another CopyOnWrite. Does not transfer ownership (this
+   * CopyOnWrite is not the owner). */
+  CopyOnWrite &operator=(const CopyOnWrite &other) {
+    element_ptr_ = other.element_ptr_;
+    is_owner_ = false;
+    return *this;
+  }
+
+  /** Copy assignment of a temporary CopyOnWrite. If the temporary is owner then
+   * ownerships is transferred to this CopyOnWrite. Otherwise this CopyOnWrite
+   * does not become the owner. */
+  CopyOnWrite &operator=(CopyOnWrite &&other) = default;
+
+  // All the dereferencing operators are overloaded to return a const element
+  // reference. There is no differentiation between const and non-const member
+  // function behavior because an implicit copy creation on non-const
+  // dereferencing would most likely result in excessive copying. For that
+  // reason
+  // an explicit call to the `Write` function is required to obtain a non-const
+  // reference to element.
+  const TElement &operator*() { return *element_ptr_; }
+  const TElement &operator*() const { return *element_ptr_; }
+  const TElement &get() { return *element_ptr_; }
+  const TElement &get() const { return *element_ptr_; }
+  const TElement *operator->() { return element_ptr_.get(); }
+  const TElement *operator->() const { return element_ptr_.get(); }
+
+  /** Indicates if this CopyOnWrite object is the owner of it's element. */
+  bool is_owner() const { return is_owner_; };
+
+  /**
+   * If this CopyOnWrite is the owner of it's element, a non-const reference to
+   * is returned. If this CopyOnWrite is not the owner, then the element is
+   * copied and this CopyOnWrite becomes the owner.
+   */
+  TElement &Write() {
+    if (is_owner_) return *element_ptr_;
+    element_ptr_ = std::shared_ptr<TElement>(new TElement(*element_ptr_));
+    is_owner_ = true;
+    return *element_ptr_;
+  };
+
+ private:
+  std::shared_ptr<TElement> element_ptr_;
+  bool is_owner_{false};
+};
+}  // namespace utils
diff --git a/poc/utils/utils_copy_on_write.cpp b/poc/utils/utils_copy_on_write.cpp
new file mode 100644
index 000000000..856ec230e
--- /dev/null
+++ b/poc/utils/utils_copy_on_write.cpp
@@ -0,0 +1,67 @@
+#include "gtest/gtest.h"
+
+#include "utils/copy_on_write.hpp"
+
+TEST(CopyOneWrite, Dereferencing) {
+  utils::CopyOnWrite<std::string> number("42");
+  EXPECT_EQ((*number).size(), 2);
+  EXPECT_EQ(number.get().size(), 2);
+  EXPECT_EQ(number->size(), 2);
+}
+
+TEST(CopyOneWrite, Ownership) {
+  utils::CopyOnWrite<std::string> number("42");
+  EXPECT_TRUE(number.is_owner());
+
+  utils::CopyOnWrite<std::string> copy_constructed(number);
+  auto copy_assigned = number;
+  EXPECT_FALSE(copy_constructed.is_owner());
+  EXPECT_FALSE(copy_assigned.is_owner());
+  EXPECT_TRUE(number.is_owner());
+}
+
+TEST(CopyOneWrite, OwnershipFromTemporary) {
+  utils::CopyOnWrite<std::string> copy_constructed(
+      utils::CopyOnWrite<std::string>("42"));
+  auto copy_assigned = utils::CopyOnWrite<std::string>("42");
+  EXPECT_TRUE(copy_constructed.is_owner());
+  EXPECT_TRUE(copy_assigned.is_owner());
+}
+
+struct DestructorCounter {
+  DestructorCounter(int &counter) : counter_(counter) {}
+  ~DestructorCounter() {
+    counter_++;
+  }
+  private:
+    int &counter_;
+};
+
+TEST(CopyOnWrite, ElementDestruction) {
+  int counter = 0;
+  std::vector<utils::CopyOnWrite<DestructorCounter>> initial_owner;
+  initial_owner.emplace_back(counter);
+  {
+    auto copy = initial_owner[0];
+    EXPECT_EQ(counter, 0);
+    initial_owner.clear();
+    EXPECT_EQ(counter, 0);
+  }
+  EXPECT_EQ(counter, 1);
+}
+
+TEST(CopyOneWrite, Copy) {
+  utils::CopyOnWrite<std::string> number("42");
+  auto copy = number;
+  EXPECT_EQ(*copy, "42");
+
+  // modification by owner
+  number.Write().resize(1);
+  EXPECT_EQ(*number, "4");
+  EXPECT_EQ(*copy, "4");
+
+  // modification by copy
+  copy.Write().resize(2, '3');
+  EXPECT_EQ(*number, "4");
+  EXPECT_EQ(*copy, "43");
+}