Commit 8871fd42 authored by Andrii Grynenko's avatar Andrii Grynenko Committed by Facebook Github Bot

MasterPtr

Summary: Shared-ptr, which should be explicitly joined and waits for all other instances to be destroyed.

Reviewed By: spalamarchuk

Differential Revision: D18696536

fbshipit-source-id: 9d7b0b960a00095be7df033680c7a116c358637c
parent fac0cb2b
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* 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 <memory>
#include <folly/synchronization/Baton.h>
#include <glog/logging.h>
namespace folly {
template <typename T>
class MasterPtrRef;
/**
* MasterPtr should be used to achieve deterministic destruction of objects with
* shared ownership.
* Once an object is managed by a MasterPtr, shared_ptrs can be obtained
* pointing to that object. However destroying those shared_ptrs will never call
* the object destructor inline. To destroy the object, join() method should be
* called on MasterPtr which will wait for all shared_ptrs to be released and
* then call the object destructor inline.
*/
template <typename T>
class MasterPtr {
public:
MasterPtr();
MasterPtr(std::unique_ptr<T> ptr) {
set(std::move(ptr));
}
~MasterPtr() {
if (innerPtr_) {
LOG(FATAL) << "MasterPtr has to be joined explicitly.";
}
}
// Attempts to lock a pointer. Returns null if pointer is not set or if join()
// was called (even if the call to join() hasn't returned yet).
std::shared_ptr<T> lock() const {
if (auto outerPtr = outerPtrWeak_.lock()) {
return *outerPtr;
}
return nullptr;
}
// Waits until all the refereces obtained via lock() are released. Then
// destroys the object in the current thread.
// Can not be called concurrently with set().
void join() {
if (!innerPtr_) {
return;
}
outerPtrShared_.reset();
joinBaton_.wait();
innerPtr_.reset();
}
// Sets the pointer. Can not be called concurrently with lock() or join() or
// ref().
void set(std::unique_ptr<T> ptr) {
if (innerPtr_) {
LOG(FATAL) << "MasterPtr has to be joined before being set.";
}
if (!ptr) {
return;
}
innerPtr_ = std::move(ptr);
joinBaton_.reset();
auto innerPtrShared =
std::shared_ptr<T>(innerPtr_.get(), [&](T*) { joinBaton_.post(); });
outerPtrShared_ =
std::make_shared<std::shared_ptr<T>>(std::move(innerPtrShared));
outerPtrWeak_ = outerPtrShared_;
}
// Gets a non-owning reference to the pointer. join() does *NOT* wait for
// outstanding MasterPtrRef objects to be released.
MasterPtrRef<T> ref() const {
return MasterPtrRef<T>(outerPtrWeak_);
}
private:
friend class MasterPtrRef<T>;
folly::Baton<> joinBaton_;
std::shared_ptr<std::shared_ptr<T>> outerPtrShared_;
std::weak_ptr<std::shared_ptr<T>> outerPtrWeak_;
std::unique_ptr<T> innerPtr_;
};
template <typename T>
class MasterPtrRef {
public:
// Attempts to lock a pointer. Returns null if pointer is not set or if join()
// was called (even if the call to join() hasn't returned yet).
std::shared_ptr<T> lock() const {
if (auto outerPtr = outerPtrWeak_.lock()) {
return *outerPtr;
}
return nullptr;
}
private:
friend class MasterPtr<T>;
/* implicit */ MasterPtrRef(std::weak_ptr<std::shared_ptr<T>> outerPtrWeak)
: outerPtrWeak_(std::move(outerPtrWeak)) {}
std::weak_ptr<std::shared_ptr<T>> outerPtrWeak_;
};
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* 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 <future>
#include <folly/experimental/MasterPtr.h>
#include <folly/portability/GTest.h>
#include <folly/synchronization/Baton.h>
TEST(MasterPtrTest, Basic) {
auto ptr = std::make_unique<int>(42);
auto rawPtr = ptr.get();
folly::MasterPtr<int> masterPtr(std::move(ptr));
auto masterPtrRef = masterPtr.ref();
auto lockedPtr1 = masterPtr.lock();
auto lockedPtr2 = masterPtrRef.lock();
EXPECT_EQ(lockedPtr1.get(), rawPtr);
EXPECT_EQ(lockedPtr2.get(), rawPtr);
EXPECT_EQ(lockedPtr1.use_count(), 3);
EXPECT_EQ(lockedPtr2.use_count(), 3);
auto joinFuture = std::async(std::launch::async, [&] { masterPtr.join(); });
auto lockFailFuture = std::async(std::launch::async, [&] {
while (masterPtr.lock()) {
std::this_thread::yield();
}
});
EXPECT_EQ(
lockFailFuture.wait_for(std::chrono::milliseconds{100}),
std::future_status::ready);
EXPECT_EQ(lockedPtr1.use_count(), 2);
EXPECT_EQ(lockedPtr2.use_count(), 2);
EXPECT_EQ(masterPtr.lock().get(), nullptr);
EXPECT_EQ(masterPtrRef.lock().get(), nullptr);
EXPECT_EQ(
joinFuture.wait_for(std::chrono::milliseconds{100}),
std::future_status::timeout);
lockedPtr1.reset();
lockedPtr2.reset();
EXPECT_EQ(
joinFuture.wait_for(std::chrono::milliseconds{100}),
std::future_status::ready);
}
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