concurrent_factory.hpp 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. /* Copyright 2024 Joaquin M Lopez Munoz.
  2. * Distributed under the Boost Software License, Version 1.0.
  3. * (See accompanying file LICENSE_1_0.txt or copy at
  4. * http://www.boost.org/LICENSE_1_0.txt)
  5. *
  6. * See http://www.boost.org/libs/flyweight for library home page.
  7. */
  8. #ifndef BOOST_FLYWEIGHT_CONCURRENT_FACTORY_HPP
  9. #define BOOST_FLYWEIGHT_CONCURRENT_FACTORY_HPP
  10. #if defined(_MSC_VER)
  11. #pragma once
  12. #endif
  13. #include <boost/config.hpp> /* keep it first to prevent nasty warns in MSVC */
  14. #include <atomic>
  15. #include <boost/assert.hpp>
  16. #include <boost/core/allocator_access.hpp>
  17. #include <boost/core/invoke_swap.hpp>
  18. #include <boost/flyweight/concurrent_factory_fwd.hpp>
  19. #include <boost/flyweight/factory_tag.hpp>
  20. #include <boost/flyweight/detail/is_placeholder_expr.hpp>
  21. #include <boost/unordered/concurrent_node_set.hpp>
  22. #include <chrono>
  23. #include <condition_variable>
  24. #include <mutex>
  25. #include <thread>
  26. #include <type_traits>
  27. #include <utility>
  28. /* flyweight factory based on boost::concurent_node_set */
  29. namespace boost{
  30. namespace flyweights{
  31. namespace concurrent_factory_detail{
  32. template<typename Entry>
  33. struct refcounted_entry:Entry
  34. {
  35. explicit refcounted_entry(Entry&& x):Entry{std::move(x)}{}
  36. refcounted_entry(refcounted_entry&& x):
  37. Entry{std::move(static_cast<Entry&>(x))}{}
  38. long count()const{return ref;}
  39. void add_ref()const{++ref;}
  40. void release()const{--ref;}
  41. private:
  42. mutable std::atomic_long ref{0};
  43. };
  44. template<typename RefcountedEntry>
  45. class refcounted_handle
  46. {
  47. public:
  48. refcounted_handle(const refcounted_handle& x):p{x.p}{p->add_ref();}
  49. ~refcounted_handle(){p->release();}
  50. refcounted_handle& operator=(refcounted_handle x)
  51. {
  52. boost::core::invoke_swap(p,x.p);
  53. return *this;
  54. }
  55. const RefcountedEntry& get()const{return *p;}
  56. private:
  57. template<typename,typename,typename,typename,typename>
  58. friend class concurrent_factory_class_impl;
  59. refcounted_handle(const RefcountedEntry* p_):p{p_}
  60. {
  61. /* Doesn't add ref, refcount already incremented by
  62. * concurrent_factory_class_impl before calling this ctor.
  63. */
  64. BOOST_ASSERT(p!=nullptr);
  65. BOOST_ASSERT(p->count()>0);
  66. }
  67. const RefcountedEntry* p;
  68. };
  69. template<
  70. typename Entry,typename Key,
  71. typename Hash,typename Pred,typename Allocator
  72. >
  73. class concurrent_factory_class_impl:public factory_marker
  74. {
  75. using entry_type=refcounted_entry<Entry>;
  76. using unrebound_allocator_type=typename std::conditional<
  77. mpl::is_na<Allocator>::value,
  78. std::allocator<entry_type>,
  79. Allocator
  80. >::type;
  81. using container_type=boost::concurrent_node_set<
  82. entry_type,
  83. typename std::conditional<
  84. mpl::is_na<Hash>::value,
  85. boost::hash<Key>,
  86. Hash
  87. >::type,
  88. typename std::conditional<
  89. mpl::is_na<Pred>::value,
  90. std::equal_to<Key>,
  91. Pred
  92. >::type,
  93. boost::allocator_rebind_t<unrebound_allocator_type,entry_type>
  94. >;
  95. public:
  96. using handle_type=refcounted_handle<entry_type>;
  97. concurrent_factory_class_impl():gc{[this]{
  98. /* Garbage collector. Traverses the container every gc_time and lockedly
  99. * erases entries without any external reference.
  100. */
  101. constexpr auto gc_time=std::chrono::seconds(1);
  102. for(;;){
  103. {
  104. std::unique_lock<std::mutex> lk{m};
  105. if(cv.wait_for(lk,gc_time,[&]{return stop;}))return;
  106. }
  107. cont.erase_if([&](const entry_type& x){return !x.count();});
  108. }
  109. }}
  110. {}
  111. ~concurrent_factory_class_impl()
  112. {
  113. /* shut the garbage collector down */
  114. {
  115. std::unique_lock<std::mutex> lk{m};
  116. stop=true;
  117. }
  118. cv.notify_one();
  119. gc.join();
  120. }
  121. /* Instead of insert, concurrent_factory provides the undocumented extension
  122. * insert_and_visit, accessible through ADL (see global insert_and_visit
  123. * below). This ensures that visitation happens in a locked environment
  124. * even if no external locking is specified.
  125. */
  126. template<typename F>
  127. handle_type insert_and_visit(Entry&& x,F f)
  128. {
  129. const entry_type* p=nullptr;
  130. auto g=[&p,&f](const entry_type& x){
  131. f(static_cast<const Entry&>(x));
  132. x.add_ref();
  133. p=std::addressof(x);
  134. };
  135. cont.insert_and_visit(entry_type{std::move(x)},g,g);
  136. return {p};
  137. }
  138. void erase(handle_type)
  139. {
  140. /* unused entries taken care of by garbage collector */
  141. }
  142. static const Entry& entry(handle_type h){return h.get();}
  143. private:
  144. container_type cont;
  145. std::mutex m;
  146. std::condition_variable cv;
  147. bool stop=false;
  148. std::thread gc;
  149. };
  150. struct concurrent_factory_class_empty_base:factory_marker{};
  151. template<
  152. typename Entry,typename Key,
  153. typename Hash,typename Pred,typename Allocator
  154. >
  155. struct check_concurrent_factory_class_params{};
  156. } /* namespace concurrent_factory_detail */
  157. template<
  158. typename Entry,typename Key,
  159. typename Hash,typename Pred,typename Allocator
  160. >
  161. class concurrent_factory_class:
  162. /* boost::mpl::apply may instantiate concurrent_factory_class<...> even when
  163. * the type is a lambda expression, so we need to guard against the
  164. * implementation defining nonsensical typedefs based on placeholder params.
  165. */
  166. public std::conditional<
  167. boost::flyweights::detail::is_placeholder_expression<
  168. concurrent_factory_detail::check_concurrent_factory_class_params<
  169. Entry,Key,Hash,Pred,Allocator
  170. >
  171. >::value,
  172. concurrent_factory_detail::concurrent_factory_class_empty_base,
  173. concurrent_factory_detail::concurrent_factory_class_impl<
  174. Entry,Key,Hash,Pred,Allocator
  175. >
  176. >::type
  177. {};
  178. template<
  179. typename Entry,typename Key,
  180. typename Hash,typename Pred,typename Allocator,
  181. typename F
  182. >
  183. typename concurrent_factory_class<Entry,Key,Hash,Pred,Allocator>::handle_type
  184. insert_and_visit(
  185. concurrent_factory_class<Entry,Key,Hash,Pred,Allocator>& fac,Entry&& x,F f)
  186. {
  187. return fac.insert_and_visit(std::move(x),f);
  188. }
  189. /* concurrent_factory_class specifier */
  190. template<
  191. typename Hash,typename Pred,typename Allocator
  192. BOOST_FLYWEIGHT_NOT_A_PLACEHOLDER_EXPRESSION_DEF
  193. >
  194. struct concurrent_factory:factory_marker
  195. {
  196. template<typename Entry,typename Key>
  197. struct apply:
  198. mpl::apply2<
  199. concurrent_factory_class<
  200. boost::mpl::_1,boost::mpl::_2,Hash,Pred,Allocator
  201. >,
  202. Entry,Key
  203. >
  204. {};
  205. };
  206. } /* namespace flyweights */
  207. } /* namespace boost */
  208. #endif