--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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();
+}