/* * 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 #include #include #include #include #include namespace folly { template class EnablePrimaryFromThis; template class PrimaryPtr; template class PrimaryPtrRef; namespace detail { struct publicallyDerivedFromEnablePrimaryFromThis_fn { template void operator()(const EnablePrimaryFromThis&) const {} }; } // namespace detail template constexpr bool is_enable_master_from_this_v = folly:: is_invocable_v; template using is_enable_master_from_this = std::bool_constant>; /** * EnablePrimaryFromThis provides an object with appropriate access to the * functionality of the PrimaryPtr holding this. */ template class EnablePrimaryFromThis { // initializes members when the PrimaryPtr for this is constructed // // used by the PrimaryPtr for this, to invoke the EnablePrimaryFromThis base // of T, if it exists. template < class O, class Master, std::enable_if_t, int> = 0> static void set(EnablePrimaryFromThis* that, Master& m) { that->outerPtrWeak_ = m.outerPtrWeak_; } template < class O, class Master, std::enable_if_t, int> = 0> static void set(O*, Master&) {} public: // Gets a non-owning reference to the pointer. PrimaryPtr::join() and the // PrimaryPtr::cleanup() work do *NOT* wait for outstanding PrimaryPtrRef // objects to be released. PrimaryPtrRef masterRefFromThis() { return PrimaryPtrRef(outerPtrWeak_); } // Gets a non-owning const reference to the pointer. PrimaryPtr::join() and // the PrimaryPtr::cleanup() work do *NOT* wait for outstanding PrimaryPtrRef // objects to be released. PrimaryPtrRef masterRefFromThis() const { return PrimaryPtrRef(outerPtrWeak_); } // Attempts to lock a pointer. Returns null if pointer is not set or if // PrimaryPtr::join() was called or the PrimaryPtr::cleanup() task was started // (even if the call to PrimaryPtr::join() hasn't returned yet and the // PrimaryPtr::cleanup() task has not completed yet). std::shared_ptr masterLockFromThis() { if (auto outerPtr = outerPtrWeak_.lock()) { return *outerPtr; } return nullptr; } // Attempts to lock a pointer. Returns null if pointer is not set or if // PrimaryPtr::join() was called or the PrimaryPtr::cleanup() task was started // (even if the call to PrimaryPtr::join() hasn't returned yet and the // PrimaryPtr::cleanup() task has not completed yet). std::shared_ptr masterLockFromThis() const { if (!*this) { return nullptr; } if (auto outerPtr = outerPtrWeak_.lock()) { return *outerPtr; } return nullptr; } private: template friend class PrimaryPtr; std::weak_ptr> outerPtrWeak_; }; /** * PrimaryPtr should be used to achieve deterministic destruction of objects * with shared ownership. Once an object is managed by a PrimaryPtr, 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 must be called on PrimaryPtr or the task returned from cleanup() must * be completed, which will wait for all shared_ptrs to be released and then * call the object destructor on the caller supplied execution context. */ template class PrimaryPtr { // retrieves nested cleanup() work from innerPtr_. Called when the PrimaryPtr // cleanup() task has finished waiting for outstanding references // template , int> = 0> static folly::SemiFuture getCleanup(Cleanup* cleanup) { return std::move(*cleanup).cleanup(); } template , int> = 0> static folly::SemiFuture getCleanup(O*) { return folly::makeSemiFuture(); } public: PrimaryPtr() = delete; template PrimaryPtr(std::unique_ptr ptr) { set(std::move(ptr)); } ~PrimaryPtr() { if (*this) { LOG(FATAL) << "PrimaryPtr has to be joined explicitly."; } } explicit operator bool() const { return !!innerPtr_; } // Attempts to lock a pointer. Returns null if pointer is not set or if join() // was called or the cleanup() task was started (even if the call to join() // hasn't returned yet and the cleanup() task has not completed yet). std::shared_ptr lock() const { if (!*this) { return nullptr; } 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 (!*this) { return; } this->cleanup().get(); } // Returns: a SemiFuture that waits until all the refereces obtained via // lock() are released. Then destroys the object on the Executor provided to // the SemiFuture. // // The returned SemiFuture must run to completion before calling set() // folly::SemiFuture cleanup() { return folly::makeSemiFuture() // clear outerPtrShared_ after cleanup is started // to disable further calls to lock(). // then wait for outstanding references. .deferValue([this](folly::Unit) { if (!this->outerPtrShared_) { LOG(FATAL) << "Cleanup already run - lock() was previouly disabled."; } this->outerPtrShared_.reset(); return std::move(this->unreferenced_); }) // start cleanup tasks .deferValue([this](folly::Unit) { return getCleanup(innerPtr_.get()); }) .defer([this](folly::Try r) { if (r.hasException()) { LOG(FATAL) << "Cleanup actions must be noexcept."; } this->innerPtr_.reset(); }); } // Sets the pointer. Can not be called concurrently with lock() or join() or // ref() or while the SemiFuture returned from cleanup() is running. template void set(std::unique_ptr ptr) { if (*this) { LOG(FATAL) << "PrimaryPtr has to be joined before being set."; } if (!ptr) { return; } auto rawPtr = ptr.get(); innerPtr_ = std::unique_ptr>{ ptr.release(), [d = ptr.get_deleter(), rawPtr](T*) mutable { d(rawPtr); }}; auto referencesContract = folly::makePromiseContract(); unreferenced_ = std::move(std::get<1>(referencesContract)); // The deleter object needs to be copyable in std::shared_ptr on some // platform. To work around this limitation we can slightly tweak the // semantics of deleter copy constructor and check we always use this // object at most once. class LastReference { public: LastReference(Promise&& p) : p_(std::move(p)) {} LastReference(LastReference&&) = default; LastReference(LastReference& other) : LastReference(std::move(other)) {} void operator()(T*) { DCHECK(!p_.isFulfilled()); p_.setValue(); } private: Promise p_; }; auto innerPtrShared = std::shared_ptr( innerPtr_.get(), LastReference{std::move(std::get<0>(referencesContract))}); outerPtrWeak_ = outerPtrShared_ = std::make_shared>(innerPtrShared); // attaches optional EnablePrimaryFromThis base of innerPtr_ to this // PrimaryPtr EnablePrimaryFromThis::set(innerPtr_.get(), *this); } // Gets a non-owning reference to the pointer. join() and the cleanup() work // do *NOT* wait for outstanding PrimaryPtrRef objects to be released. PrimaryPtrRef ref() const { return PrimaryPtrRef(outerPtrWeak_); } private: template friend class EnablePrimaryFromThis; friend class PrimaryPtrRef; folly::SemiFuture unreferenced_; std::shared_ptr> outerPtrShared_; std::weak_ptr> outerPtrWeak_; std::unique_ptr> innerPtr_; }; /** * PrimaryPtrRef is a non-owning reference to the pointer. PrimaryPtr::join() * and the PrimaryPtr::cleanup() work do *NOT* wait for outstanding * PrimaryPtrRef objects to be released. */ template class PrimaryPtrRef { public: // Attempts to lock a pointer. Returns null if pointer is not set or if // join() was called or cleanup() work was started (even if the call to join() // hasn't returned yet or the cleanup() work has not completed yet). std::shared_ptr lock() const { if (auto outerPtr = outerPtrWeak_.lock()) { return *outerPtr; } return nullptr; } private: template friend class EnablePrimaryFromThis; template friend class PrimaryPtr; /* implicit */ PrimaryPtrRef(std::weak_ptr> outerPtrWeak) : outerPtrWeak_(std::move(outerPtrWeak)) {} std::weak_ptr> outerPtrWeak_; }; } // namespace folly