SynchronizedPtr
authorPhil Willoughby <philwill@fb.com>
Mon, 21 Aug 2017 12:07:57 +0000 (05:07 -0700)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Mon, 21 Aug 2017 12:19:22 +0000 (05:19 -0700)
Summary: Fairly minimal API to start with. Refer to the comments for the rationale/usage.

Reviewed By: yfeldblum

Differential Revision: D5554854

fbshipit-source-id: 173bf7c40e70b55bf6afb8c4bc9e83d48f90b6ee

folly/Makefile.am
folly/SynchronizedPtr.h [new file with mode: 0644]
folly/test/Makefile.am
folly/test/SynchronizedPtrTest.cpp [new file with mode: 0644]

index e77c704215b8d7424c0de27610bbedd75de77005..f1872fea5b62d859218a451a4b0c0e4df5f96286 100644 (file)
@@ -409,6 +409,7 @@ nobase_follyinclude_HEADERS = \
        String-inl.h \
        Subprocess.h \
        Synchronized.h \
+       SynchronizedPtr.h \
        test/FBStringTestBenchmarks.cpp.h \
        test/FBVectorTestBenchmarks.cpp.h \
        test/function_benchmark/benchmark_impl.h \
diff --git a/folly/SynchronizedPtr.h b/folly/SynchronizedPtr.h
new file mode 100644 (file)
index 0000000..9a9ea12
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2004-present Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <folly/Synchronized.h>
+
+/* `SynchronizedPtr` is a variation on the `Synchronized` idea that's useful for
+ * some cases where you want to protect a pointed-to object (or an object within
+ * some pointer-like wrapper). If you would otherwise need to use
+ * `Synchronized<smart_ptr<Synchronized<T>>>` consider using
+ * `SynchronizedPtr<smart_ptr<T>>`as it is a bit easier to use and it works when
+ * you want the `T` object at runtime to actually a subclass of `T`.
+ *
+ * You can access the contained `T` with `.rlock()`, and `.wlock()`, and the
+ * pointer or pointer-like wrapper with `.wlockPointer()`. The corresponding
+ * `with...` methods take a callback, invoke it with a `T const&`, `T&` or
+ * `smart_ptr<T>&` respectively, and return the callback's result.
+ */
+namespace folly {
+template <typename LockHolder, typename Element>
+struct SynchronizedPtrLockedElement {
+  explicit SynchronizedPtrLockedElement(LockHolder&& holder)
+      : holder_(std::move(holder)) {}
+
+  Element& operator*() const {
+    return **holder_;
+  }
+
+  Element* operator->() const {
+    return &**holder_;
+  }
+
+  explicit operator bool() const {
+    return static_cast<bool>(*holder_);
+  }
+
+ private:
+  LockHolder holder_;
+};
+
+template <typename PointerType, typename MutexType = SharedMutex>
+class SynchronizedPtr {
+  using inner_type = Synchronized<PointerType, MutexType>;
+  inner_type inner_;
+
+ public:
+  using pointer_type = PointerType;
+  using element_type = typename std::pointer_traits<pointer_type>::element_type;
+  using const_element_type = typename std::add_const<element_type>::type;
+  using read_locked_element = SynchronizedPtrLockedElement<
+      typename inner_type::ConstLockedPtr,
+      const_element_type>;
+  using write_locked_element = SynchronizedPtrLockedElement<
+      typename inner_type::LockedPtr,
+      element_type>;
+  using write_locked_pointer = typename inner_type::LockedPtr;
+
+  template <typename... Args>
+  explicit SynchronizedPtr(Args... args)
+      : inner_(std::forward<Args>(args)...) {}
+
+  SynchronizedPtr() = default;
+  SynchronizedPtr(SynchronizedPtr const&) = default;
+  SynchronizedPtr(SynchronizedPtr&&) = default;
+  SynchronizedPtr& operator=(SynchronizedPtr const&) = default;
+  SynchronizedPtr& operator=(SynchronizedPtr&&) = default;
+
+  // Methods to provide appropriately locked and const-qualified access to the
+  // element.
+
+  read_locked_element rlock() const {
+    return read_locked_element(inner_.rlock());
+  }
+
+  template <class Function>
+  auto withRLock(Function&& function) const {
+    return function(*rlock());
+  }
+
+  write_locked_element wlock() {
+    return write_locked_element(inner_.wlock());
+  }
+
+  template <class Function>
+  auto withWLock(Function&& function) {
+    return function(*wlock());
+  }
+
+  // Methods to provide write-locked access to the pointer. We deliberately make
+  // it difficult to get a read-locked pointer because that provides read-locked
+  // non-const access to the element, and the purpose of this class is to
+  // discourage that.
+  write_locked_pointer wlockPointer() {
+    return inner_.wlock();
+  }
+
+  template <class Function>
+  auto withWLockPointer(Function&& function) {
+    return function(*wlockPointer());
+  }
+};
+} // namespace folly
index b10f9d231288d6bc035d3e65377189dcb6a1a63a..ed0fd9775a261fc2ac0827f05abefc97d8aeffb2 100644 (file)
@@ -161,6 +161,10 @@ synchronized_test_SOURCES = SynchronizedTest.cpp
 synchronized_test_LDADD = libfollytestmain.la
 TESTS += synchronized_test
 
+synchronized_ptr_test_SOURCES = SynchronizedPtrTest.cpp
+synchronized_ptr_test_LDADD = libfollytestmain.la
+TESTS += synchronized_ptr_test
+
 lock_traits_test_SOURCES = LockTraitsTest.cpp
 lock_traits_test_LDADD = libfollytestmain.la
 TESTS += lock_traits_test
diff --git a/folly/test/SynchronizedPtrTest.cpp b/folly/test/SynchronizedPtrTest.cpp
new file mode 100644 (file)
index 0000000..ab593e2
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2017 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/SynchronizedPtr.h>
+
+#include <folly/Optional.h>
+#include <folly/RWSpinLock.h>
+#include <folly/Replaceable.h>
+#include <folly/portability/GTest.h>
+
+template <typename SPtr>
+void basics(SPtr& sptr) {
+  EXPECT_TRUE((std::is_same<int const&, decltype(*sptr.rlock())>::value));
+  auto initialValue = *sptr.rlock();
+  bool rlockedTypeOK{false};
+  sptr.withRLock([&](auto&& value) {
+    rlockedTypeOK = std::is_same<int const&, decltype(value)>::value;
+  });
+  EXPECT_TRUE(rlockedTypeOK);
+  EXPECT_TRUE((std::is_same<int&, decltype(*sptr.wlock())>::value));
+  bool wlockedTypeOK{false};
+  sptr.withWLock([&](auto&& value) {
+    wlockedTypeOK = std::is_same<int&, decltype(value)>::value;
+    ++value;
+  });
+  EXPECT_TRUE(wlockedTypeOK);
+  EXPECT_EQ(initialValue + 1, *sptr.rlock());
+}
+
+TEST(SynchronizedPtrTest, Shared) {
+  folly::SynchronizedPtr<std::shared_ptr<int>> pInt{std::make_shared<int>(0)};
+  basics(pInt);
+}
+
+TEST(SynchronizedPtrTest, UniqueBasic) {
+  folly::SynchronizedPtr<std::unique_ptr<int>> pInt{std::make_unique<int>(0)};
+  basics(pInt);
+}
+
+TEST(SynchronizedPtrTest, UniqueDeleter) {
+  bool calledDeleter = false;
+  auto x = [&](int* ptr) {
+    delete ptr;
+    calledDeleter = true;
+  };
+  {
+    folly::SynchronizedPtr<std::unique_ptr<int, decltype(x)>> pInt{
+        std::unique_ptr<int, decltype(x)>(new int(0), x)};
+    basics(pInt);
+    EXPECT_TRUE((std::is_same<
+                 std::unique_ptr<int, decltype(x)>&,
+                 decltype(*pInt.wlockPointer())>::value));
+    pInt.wlockPointer()->reset(new int(5));
+    EXPECT_TRUE(calledDeleter);
+    calledDeleter = false;
+  }
+  EXPECT_TRUE(calledDeleter);
+}
+
+TEST(SynchronizedPtrTest, Replaceable) {
+  folly::SynchronizedPtr<folly::Replaceable<int>> pInt{0};
+  folly::SynchronizedPtr<folly::Replaceable<int const>> pcInt{2};
+  basics(pInt);
+  EXPECT_TRUE(
+      (std::is_same<folly::Replaceable<int>&, decltype(*pInt.wlockPointer())>::
+           value));
+  EXPECT_TRUE((std::is_same<
+               folly::Replaceable<int const>&,
+               decltype(*pcInt.wlockPointer())>::value));
+  pcInt.withWLockPointer([](auto&& ptr) {
+    EXPECT_TRUE(
+        (std::is_same<folly::Replaceable<int const>&, decltype(ptr)>::value));
+    ptr.emplace(4);
+  });
+  EXPECT_EQ(4, *pcInt.rlock());
+}
+
+TEST(SynchronizedPtrTest, Optional) {
+  folly::SynchronizedPtr<folly::Optional<int>, folly::RWSpinLock> pInt{0};
+  basics(pInt);
+  EXPECT_TRUE(
+      (std::is_same<folly::Optional<int>&, decltype(*pInt.wlockPointer())>::
+           value));
+  EXPECT_TRUE(static_cast<bool>(pInt.rlock()));
+  pInt.withWLockPointer([](auto&& ptr) {
+    EXPECT_TRUE((std::is_same<folly::Optional<int>&, decltype(ptr)>::value));
+    ptr.clear();
+  });
+  EXPECT_FALSE(static_cast<bool>(pInt.rlock()));
+}
+
+TEST(SynchronizedPtrTest, Virtual) {
+  struct A {
+    virtual void poke(bool&) const {}
+    virtual ~A() = default;
+  };
+  struct B : A {
+    void poke(bool& b) const override {
+      b = true;
+    }
+  };
+  folly::SynchronizedPtr<A*> pA{new B()};
+  bool itWorks = false;
+  pA.rlock()->poke(itWorks);
+  EXPECT_TRUE(itWorks);
+  itWorks = false;
+  pA.wlock()->poke(itWorks);
+  EXPECT_TRUE(itWorks);
+  pA.withWLockPointer([](auto&& ptr) {
+    EXPECT_TRUE((std::is_same<A*&, decltype(ptr)>::value));
+    delete ptr;
+    ptr = new B();
+  });
+  {
+    auto lockedPtr = pA.wlockPointer();
+    EXPECT_TRUE((std::is_same<A*&, decltype(*lockedPtr)>::value));
+    delete *lockedPtr;
+    *lockedPtr = new B();
+  }
+  itWorks = false;
+  pA.wlock()->poke(itWorks);
+  EXPECT_TRUE(itWorks);
+  delete *pA.wlockPointer();
+}