Commit 4560ebca authored by Phil Willoughby's avatar Phil Willoughby Committed by Facebook Github Bot

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
parent f2ddd0ef
......@@ -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 \
......
/*
* 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
......@@ -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
......
/*
* 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();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment