LCOV - code coverage report
Current view: top level - source/common/shared_pool - shared_pool.h (source / functions) Hit Total Coverage
Test: coverage.dat Lines: 42 54 77.8 %
Date: 2024-01-05 06:35:25 Functions: 13 30 43.3 %

          Line data    Source code
       1             : #pragma once
       2             : 
       3             : #include <functional>
       4             : #include <memory>
       5             : #include <thread>
       6             : #include <type_traits>
       7             : 
       8             : #include "envoy/event/dispatcher.h"
       9             : #include "envoy/singleton/instance.h"
      10             : 
      11             : #include "source/common/common/assert.h"
      12             : #include "source/common/common/non_copyable.h"
      13             : #include "source/common/common/thread_synchronizer.h"
      14             : 
      15             : #include "absl/container/flat_hash_set.h"
      16             : 
      17             : namespace Envoy {
      18             : namespace SharedPool {
      19             : 
      20             : /**
      21             :  * Used to share objects that have the same content.
      22             :  * control the life cycle of shared objects by reference counting
      23             :  *
      24             :  * Note:  ObjectSharedPool needs to be created in the main thread,
      25             :  * all the member methods can only be called in the main thread,
      26             :  * it does not have the ownership of object stored internally, the internal storage is weak_ptr,
      27             :  * when the internal storage object destructor executes the custom deleter to remove its own
      28             :  * weak_ptr from the ObjectSharedPool.
      29             :  *
      30             :  * There is also a need to ensure that the thread where ObjectSharedPool's destructor is also in the
      31             :  * main thread, or that ObjectSharedPool destruct before the program exit
      32             :  */
      33             : template <typename T, typename HashFunc = std::hash<T>, typename EqualFunc = std::equal_to<T>,
      34             :           class = typename std::enable_if<std::is_copy_constructible<T>::value>::type>
      35             : class ObjectSharedPool
      36             :     : public Singleton::Instance,
      37             :       public std::enable_shared_from_this<ObjectSharedPool<T, HashFunc, EqualFunc>>,
      38             :       NonCopyable {
      39             : public:
      40             :   ObjectSharedPool(Event::Dispatcher& dispatcher)
      41         229 :       : thread_id_(std::this_thread::get_id()), dispatcher_(dispatcher) {}
      42             : 
      43         159 :   std::shared_ptr<T> getObject(const T& obj) {
      44         159 :     ASSERT(std::this_thread::get_id() == thread_id_);
      45             : 
      46             :     // Return from the object pool if we find the object there.
      47         159 :     if (auto iter = object_pool_.find(&obj); iter != object_pool_.end()) {
      48          61 :       if (auto lock_object = iter->lock(); static_cast<bool>(lock_object) == true) {
      49          61 :         return lock_object;
      50          61 :       } else {
      51             :         // Remove the weak_ptr since all associated shared_ptrs have been
      52             :         // destroyed.
      53           0 :         object_pool_.erase(iter);
      54           0 :       }
      55          61 :     }
      56             : 
      57             :     // Create a shared_ptr and add the object to the object_pool.
      58          98 :     auto this_shared_ptr = this->shared_from_this();
      59          98 :     std::shared_ptr<T> obj_shared(new T(obj), [this_shared_ptr](T* ptr) {
      60          98 :       this_shared_ptr->sync().syncPoint(ObjectSharedPool<T>::ObjectDeleterEntry);
      61          98 :       this_shared_ptr->deleteObject(ptr);
      62          98 :     });
      63          98 :     object_pool_.emplace(obj_shared);
      64          98 :     return obj_shared;
      65         159 :   }
      66             : 
      67             :   std::size_t poolSize() const {
      68             :     ASSERT(std::this_thread::get_id() == thread_id_);
      69             :     return object_pool_.size();
      70             :   }
      71             : 
      72             :   /**
      73             :    * @return a thread synchronizer object used for reproducing a race-condition in tests.
      74             :    */
      75          98 :   Thread::ThreadSynchronizer& sync() { return sync_; }
      76             :   static const char DeleteObjectOnMainThread[];
      77             :   static const char ObjectDeleterEntry[];
      78             : 
      79             :   friend class SharedPoolTest;
      80             : 
      81             : private:
      82          98 :   void deleteObject(T* ptr) {
      83          98 :     if (std::this_thread::get_id() == thread_id_) {
      84          98 :       deleteObjectOnMainThread(ptr);
      85          98 :     } else {
      86             :       // Most of the time, the object's destructor occurs in the main thread, but with some
      87             :       // exceptions, it is destructed in the worker thread. In order to keep the object_pool_ thread
      88             :       // safe, the deleteObject needs to be delivered to the main thread.
      89           0 :       auto this_shared_ptr = this->shared_from_this();
      90             :       // Used for testing to simulate some race condition scenarios
      91           0 :       sync_.syncPoint(DeleteObjectOnMainThread);
      92           0 :       dispatcher_.post([ptr, this_shared_ptr] { this_shared_ptr->deleteObjectOnMainThread(ptr); });
      93           0 :     }
      94          98 :   }
      95             : 
      96          98 :   void deleteObjectOnMainThread(T* ptr) {
      97          98 :     ASSERT(std::this_thread::get_id() == thread_id_);
      98          98 :     if (auto iter = object_pool_.find(ptr); iter != object_pool_.end()) {
      99             :       // It is possible that the entry in object_pool_ corresponds to a
     100             :       // different weak_ptr, due to a race condition in a shared_ptr being
     101             :       // destroyed on another thread, and getObject() being called on the main
     102             :       // thread.
     103          98 :       if (iter->use_count() == 0) {
     104          98 :         object_pool_.erase(iter);
     105          98 :       }
     106          98 :     }
     107             :     // Wait till here to delete the pointer because we don't want the OS to
     108             :     // reallocate the memory location before this method completes to prevent
     109             :     // "hash collisions".
     110          98 :     delete ptr;
     111          98 :   }
     112             : 
     113             :   class Element {
     114             :   public:
     115         196 :     Element(const std::shared_ptr<T>& ptr) : ptr_{ptr.get()}, weak_ptr_{ptr} {}
     116             : 
     117             :     Element() = delete;
     118             :     Element(const Element&) = delete;
     119             : 
     120           0 :     Element(Element&&) noexcept = default;
     121             : 
     122          61 :     std::shared_ptr<T> lock() const { return weak_ptr_.lock(); }
     123          98 :     long use_count() const { return weak_ptr_.use_count(); }
     124             : 
     125             :     friend struct Hash;
     126             :     friend struct Compare;
     127             : 
     128             :     struct Hash {
     129             :       using is_transparent = void; // NOLINT(readability-identifier-naming)
     130         257 :       constexpr size_t operator()(const T* ptr) const { return HashFunc{}(*ptr); }
     131          98 :       constexpr size_t operator()(const Element& element) const {
     132          98 :         return HashFunc{}(*element.ptr_);
     133          98 :       }
     134             :     };
     135             :     struct Compare {
     136             :       using is_transparent = void; // NOLINT(readability-identifier-naming)
     137           0 :       bool operator()(const Element& a, const Element& b) const {
     138           0 :         ASSERT(a.ptr_ != nullptr && b.ptr_ != nullptr);
     139           0 :         return a.ptr_ == b.ptr_ ||
     140           0 :                (a.ptr_ != nullptr && b.ptr_ != nullptr && EqualFunc{}(*a.ptr_, *b.ptr_));
     141           0 :       }
     142         159 :       bool operator()(const Element& a, const T* ptr) const {
     143         159 :         ASSERT(a.ptr_ != nullptr && ptr != nullptr);
     144         159 :         return a.ptr_ == ptr || (a.ptr_ != nullptr && ptr != nullptr && EqualFunc{}(*a.ptr_, *ptr));
     145         159 :       }
     146             :     };
     147             : 
     148             :   private:
     149             :     const T* const ptr_ = nullptr; ///< This is only used to speed up
     150             :                                    ///< comparisons and should never be
     151             :                                    ///< made available outside this class.
     152             :     std::weak_ptr<T> weak_ptr_;
     153             :   };
     154             : 
     155             :   const std::thread::id thread_id_;
     156             :   absl::flat_hash_set<Element, typename Element::Hash, typename Element::Compare> object_pool_;
     157             :   // Use a multimap to allow for multiple objects with the same hash key.
     158             :   // std::unordered_multimap<size_t, std::weak_ptr<T>> object_pool_;
     159             :   Event::Dispatcher& dispatcher_;
     160             :   Thread::ThreadSynchronizer sync_;
     161             : };
     162             : 
     163             : template <typename T, typename HashFunc, typename EqualFunc, class V>
     164             : const char ObjectSharedPool<T, HashFunc, EqualFunc, V>::DeleteObjectOnMainThread[] =
     165             :     "delete-object-on-main";
     166             : 
     167             : template <typename T, typename HashFunc, typename EqualFunc, class V>
     168             : const char ObjectSharedPool<T, HashFunc, EqualFunc, V>::ObjectDeleterEntry[] = "deleter-entry";
     169             : 
     170             : } // namespace SharedPool
     171             : } // namespace Envoy

Generated by: LCOV version 1.15