From 4560ebcac260ad75fbe983b1a0a307567ee9fe49 Mon Sep 17 00:00:00 2001 From: Phil Willoughby Date: Mon, 21 Aug 2017 05:07:57 -0700 Subject: [PATCH] SynchronizedPtr 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 | 1 + folly/SynchronizedPtr.h | 116 ++++++++++++++++++++++++ folly/test/Makefile.am | 4 + folly/test/SynchronizedPtrTest.cpp | 137 +++++++++++++++++++++++++++++ 4 files changed, 258 insertions(+) create mode 100644 folly/SynchronizedPtr.h create mode 100644 folly/test/SynchronizedPtrTest.cpp diff --git a/folly/Makefile.am b/folly/Makefile.am index e77c7042..f1872fea 100644 --- a/folly/Makefile.am +++ b/folly/Makefile.am @@ -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 index 00000000..9a9ea12c --- /dev/null +++ b/folly/SynchronizedPtr.h @@ -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 + +/* `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>>` consider using + * `SynchronizedPtr>`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&` respectively, and return the callback's result. + */ +namespace folly { +template +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(*holder_); + } + + private: + LockHolder holder_; +}; + +template +class SynchronizedPtr { + using inner_type = Synchronized; + inner_type inner_; + + public: + using pointer_type = PointerType; + using element_type = typename std::pointer_traits::element_type; + using const_element_type = typename std::add_const::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 + explicit SynchronizedPtr(Args... args) + : inner_(std::forward(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 + auto withRLock(Function&& function) const { + return function(*rlock()); + } + + write_locked_element wlock() { + return write_locked_element(inner_.wlock()); + } + + template + 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 + auto withWLockPointer(Function&& function) { + return function(*wlockPointer()); + } +}; +} // namespace folly diff --git a/folly/test/Makefile.am b/folly/test/Makefile.am index b10f9d23..ed0fd977 100644 --- a/folly/test/Makefile.am +++ b/folly/test/Makefile.am @@ -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 index 00000000..ab593e2d --- /dev/null +++ b/folly/test/SynchronizedPtrTest.cpp @@ -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 + +#include +#include +#include +#include + +template +void basics(SPtr& sptr) { + EXPECT_TRUE((std::is_same::value)); + auto initialValue = *sptr.rlock(); + bool rlockedTypeOK{false}; + sptr.withRLock([&](auto&& value) { + rlockedTypeOK = std::is_same::value; + }); + EXPECT_TRUE(rlockedTypeOK); + EXPECT_TRUE((std::is_same::value)); + bool wlockedTypeOK{false}; + sptr.withWLock([&](auto&& value) { + wlockedTypeOK = std::is_same::value; + ++value; + }); + EXPECT_TRUE(wlockedTypeOK); + EXPECT_EQ(initialValue + 1, *sptr.rlock()); +} + +TEST(SynchronizedPtrTest, Shared) { + folly::SynchronizedPtr> pInt{std::make_shared(0)}; + basics(pInt); +} + +TEST(SynchronizedPtrTest, UniqueBasic) { + folly::SynchronizedPtr> pInt{std::make_unique(0)}; + basics(pInt); +} + +TEST(SynchronizedPtrTest, UniqueDeleter) { + bool calledDeleter = false; + auto x = [&](int* ptr) { + delete ptr; + calledDeleter = true; + }; + { + folly::SynchronizedPtr> pInt{ + std::unique_ptr(new int(0), x)}; + basics(pInt); + EXPECT_TRUE((std::is_same< + std::unique_ptr&, + decltype(*pInt.wlockPointer())>::value)); + pInt.wlockPointer()->reset(new int(5)); + EXPECT_TRUE(calledDeleter); + calledDeleter = false; + } + EXPECT_TRUE(calledDeleter); +} + +TEST(SynchronizedPtrTest, Replaceable) { + folly::SynchronizedPtr> pInt{0}; + folly::SynchronizedPtr> pcInt{2}; + basics(pInt); + EXPECT_TRUE( + (std::is_same&, decltype(*pInt.wlockPointer())>:: + value)); + EXPECT_TRUE((std::is_same< + folly::Replaceable&, + decltype(*pcInt.wlockPointer())>::value)); + pcInt.withWLockPointer([](auto&& ptr) { + EXPECT_TRUE( + (std::is_same&, decltype(ptr)>::value)); + ptr.emplace(4); + }); + EXPECT_EQ(4, *pcInt.rlock()); +} + +TEST(SynchronizedPtrTest, Optional) { + folly::SynchronizedPtr, folly::RWSpinLock> pInt{0}; + basics(pInt); + EXPECT_TRUE( + (std::is_same&, decltype(*pInt.wlockPointer())>:: + value)); + EXPECT_TRUE(static_cast(pInt.rlock())); + pInt.withWLockPointer([](auto&& ptr) { + EXPECT_TRUE((std::is_same&, decltype(ptr)>::value)); + ptr.clear(); + }); + EXPECT_FALSE(static_cast(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 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::value)); + delete ptr; + ptr = new B(); + }); + { + auto lockedPtr = pA.wlockPointer(); + EXPECT_TRUE((std::is_same::value)); + delete *lockedPtr; + *lockedPtr = new B(); + } + itWorks = false; + pA.wlock()->poke(itWorks); + EXPECT_TRUE(itWorks); + delete *pA.wlockPointer(); +} -- 2.34.1