100.00% Lines (53/53) 100.00% Functions (13/13)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 4   // Distributed under the Boost Software License, Version 1.0. (See accompanying
5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/capy 7   // Official repository: https://github.com/cppalliance/capy
8   // 8   //
9   9  
10   #include <boost/capy/ex/recycling_memory_resource.hpp> 10   #include <boost/capy/ex/recycling_memory_resource.hpp>
11   11  
12   #include <new> 12   #include <new>
13   #include <tuple> 13   #include <tuple>
14   14  
15   namespace boost { 15   namespace boost {
16   namespace capy { 16   namespace capy {
17   17  
18   // Instance destruction does nothing: the resource is stateless (all pools 18   // Instance destruction does nothing: the resource is stateless (all pools
19   // are static). Global-pool cleanup is owned by global()'s holder, exactly 19   // are static). Global-pool cleanup is owned by global()'s holder, exactly
20   // as in the original where the global pool's own destructor drained it, 20   // as in the original where the global pool's own destructor drained it,
21   // independent of any instance lifetime. 21   // independent of any instance lifetime.
HITCBC 22   306 recycling_memory_resource::~recycling_memory_resource() = default; 22   307 recycling_memory_resource::~recycling_memory_resource() = default;
23   23  
24   #if (defined(BOOST_CAPY_DYN_LINK) || defined(BOOST_ALL_DYN_LINK)) && \ 24   #if (defined(BOOST_CAPY_DYN_LINK) || defined(BOOST_ALL_DYN_LINK)) && \
25   !defined(BOOST_CAPY_STATIC_LINK) 25   !defined(BOOST_CAPY_STATIC_LINK)
26   26  
27   recycling_memory_resource::pool& 27   recycling_memory_resource::pool&
HITCBC 28   644008 recycling_memory_resource::local() noexcept 28   644349 recycling_memory_resource::local() noexcept
29   { 29   {
30   static thread_local pool p; 30   static thread_local pool p;
HITCBC 31   644008 return p; 31   644349 return p;
32   } 32   }
33   33  
34   bool& 34   bool&
HITCBC 35   216938 recycling_memory_resource::thread_armed() noexcept 35   216943 recycling_memory_resource::thread_armed() noexcept
36   { 36   {
37   static thread_local bool armed = false; 37   static thread_local bool armed = false;
HITCBC 38   216938 return armed; 38   216943 return armed;
39   } 39   }
40   40  
41   #endif 41   #endif
42   42  
43   namespace { 43   namespace {
44   44  
45   // Set once this thread's pool was drained at exit. Another thread_local 45   // Set once this thread's pool was drained at exit. Another thread_local
46   // destroyed later can still free frames; they must not be cached. 46   // destroyed later can still free frames; they must not be cached.
47   thread_local bool pool_drained = false; 47   thread_local bool pool_drained = false;
48   48  
49   } // namespace 49   } // namespace
50   50  
51   bool 51   bool
HITCBC 52   210859 recycling_memory_resource::arm_thread_cleanup() noexcept 52   211195 recycling_memory_resource::arm_thread_cleanup() noexcept
53   { 53   {
54   struct janitor 54   struct janitor
55   { 55   {
HITCBC 56   363 ~janitor() 56   365 ~janitor()
57   { 57   {
58   // Return this thread's cached blocks to the OS so nothing 58   // Return this thread's cached blocks to the OS so nothing
59   // leaks at thread exit. The pool has a trivial dtor, so its 59   // leaks at thread exit. The pool has a trivial dtor, so its
60   // thread-local storage is still valid here. 60   // thread-local storage is still valid here.
HITCBC 61   363 auto& lp = local(); 61   365 auto& lp = local();
HITCBC 62   2541 for(auto& b : lp.buckets) 62   2555 for(auto& b : lp.buckets)
HITCBC 63   6713 while(b.count > 0) 63   6728 while(b.count > 0)
HITCBC 64   4535 ::operator delete(b.pop()); 64   4538 ::operator delete(b.pop());
HITCBC 65   363 pool_drained = true; 65   365 pool_drained = true;
HITCBC 66   363 thread_armed() = false; 66   365 thread_armed() = false;
HITCBC 67   363 } 67   365 }
68   }; 68   };
HITCBC 69   210859 if(pool_drained) 69   211195 if(pool_drained)
HITCBC 70   5 return false; 70   5 return false;
HITCBC 71   210854 static thread_local janitor j; 71   211190 static thread_local janitor j;
HITCBC 72   210854 std::ignore = j; 72   211190 std::ignore = j;
HITCBC 73   210854 return true; 73   211190 return true;
74   } 74   }
75   75  
76   recycling_memory_resource::pool& 76   recycling_memory_resource::pool&
HITCBC 77   420081 recycling_memory_resource::global() noexcept 77   420498 recycling_memory_resource::global() noexcept
78   { 78   {
79   // Holder gives the global pool a destructor (the trivial-dtor pool type 79   // Holder gives the global pool a destructor (the trivial-dtor pool type
80   // itself must stay guard-free for local()). Runs unconditionally at 80   // itself must stay guard-free for local()). Runs unconditionally at
81   // process exit, mirroring the original global pool destructor, and is 81   // process exit, mirroring the original global pool destructor, and is
82   // locked because a worker thread may still be in a slow path. This path 82   // locked because a worker thread may still be in a slow path. This path
83   // is cold (slow paths only), so the holder's guard costs nothing hot. 83   // is cold (slow paths only), so the holder's guard costs nothing hot.
84   struct holder 84   struct holder
85   { 85   {
86   pool p; 86   pool p;
87   87  
HITCBC 88   33 ~holder() 88   34 ~holder()
89   { 89   {
HITCBC 90   33 std::lock_guard<std::mutex> lock(global_mutex()); 90   34 std::lock_guard<std::mutex> lock(global_mutex());
HITCBC 91   231 for(auto& b : p.buckets) 91   238 for(auto& b : p.buckets)
HITCBC 92   321 while(b.count > 0) 92   327 while(b.count > 0)
HITCBC 93   123 ::operator delete(b.pop()); 93   123 ::operator delete(b.pop());
HITCBC 94   33 } 94   34 }
95   }; 95   };
HITCBC 96   420081 static holder h; 96   420498 static holder h;
HITCBC 97   420081 return h.p; 97   420498 return h.p;
98   } 98   }
99   99  
100   std::mutex& 100   std::mutex&
HITCBC 101   420114 recycling_memory_resource::global_mutex() noexcept 101   420532 recycling_memory_resource::global_mutex() noexcept
102   { 102   {
103   // Never destroyed: it is locked during process-exit teardown (in 103   // Never destroyed: it is locked during process-exit teardown (in
104   // global()'s holder destructor), after a function-local `static 104   // global()'s holder destructor), after a function-local `static
105   // std::mutex` could itself have been destroyed. Placement-new into 105   // std::mutex` could itself have been destroyed. Placement-new into
106   // static storage owns no heap allocation, so there is nothing to leak. 106   // static storage owns no heap allocation, so there is nothing to leak.
107   alignas(std::mutex) static unsigned char storage[sizeof(std::mutex)]; 107   alignas(std::mutex) static unsigned char storage[sizeof(std::mutex)];
108   static std::mutex* const mtx = 108   static std::mutex* const mtx =
HITCBC 109   420114 ::new(static_cast<void*>(storage)) std::mutex(); 109   420532 ::new(static_cast<void*>(storage)) std::mutex();
HITCBC 110   420114 return *mtx; 110   420532 return *mtx;
111   } 111   }
112   112  
113   void* 113   void*
HITCBC 114   210500 recycling_memory_resource::allocate_slow( 114   210833 recycling_memory_resource::allocate_slow(
115   std::size_t rounded, std::size_t idx) 115   std::size_t rounded, std::size_t idx)
116   { 116   {
117   // Once this thread's pool was drained at exit, take one block and 117   // Once this thread's pool was drained at exit, take one block and
118   // leave the pool empty: nothing would release a refill. 118   // leave the pool empty: nothing would release a refill.
HITCBC 119   210500 bool const cache = arm_thread_cleanup(); 119   210833 bool const cache = arm_thread_cleanup();
120   { 120   {
HITCBC 121   210500 std::lock_guard<std::mutex> lock(global_mutex()); 121   210833 std::lock_guard<std::mutex> lock(global_mutex());
HITCBC 122   210500 auto& b = global().buckets[idx]; 122   210833 auto& b = global().buckets[idx];
HITCBC 123   210500 if(auto* p = cache ? b.pop(local().buckets[idx]) : b.pop()) 123   210833 if(auto* p = cache ? b.pop(local().buckets[idx]) : b.pop())
HITCBC 124   655 return p; 124   716 return p;
HITCBC 125   210500 } 125   210833 }
HITCBC 126   209845 return ::operator new(rounded); 126   210117 return ::operator new(rounded);
127   } 127   }
128   128  
129   void 129   void
HITCBC 130   209581 recycling_memory_resource::deallocate_slow( 130   209665 recycling_memory_resource::deallocate_slow(
131   void* p, std::size_t idx) 131   void* p, std::size_t idx)
132   { 132   {
133   { 133   {
HITCBC 134   209581 std::lock_guard<std::mutex> lock(global_mutex()); 134   209665 std::lock_guard<std::mutex> lock(global_mutex());
HITCBC 135   209581 if(global().buckets[idx].push(p)) 135   209665 if(global().buckets[idx].push(p))
HITCBC 136   4394 return; 136   4209 return;
HITCBC 137   209581 } 137   209665 }
HITCBC 138   205187 ::operator delete(p); 138   205456 ::operator delete(p);
139   } 139   }
140   140  
141   void* 141   void*
HITCBC 142   102469 recycling_memory_resource::do_allocate(std::size_t bytes, std::size_t alignment) 142   102470 recycling_memory_resource::do_allocate(std::size_t bytes, std::size_t alignment)
143   { 143   {
HITCBC 144   102469 return allocate_fast(bytes, alignment); 144   102470 return allocate_fast(bytes, alignment);
145   } 145   }
146   146  
147   void 147   void
HITCBC 148   102469 recycling_memory_resource::do_deallocate(void* p, std::size_t bytes, std::size_t alignment) 148   102470 recycling_memory_resource::do_deallocate(void* p, std::size_t bytes, std::size_t alignment)
149   { 149   {
HITCBC 150   102469 deallocate_fast(p, bytes, alignment); 150   102470 deallocate_fast(p, bytes, alignment);
HITCBC 151   102469 } 151   102470 }
152   152  
153   std::pmr::memory_resource* 153   std::pmr::memory_resource*
HITCBC 154   4548 get_recycling_memory_resource() noexcept 154   4555 get_recycling_memory_resource() noexcept
155   { 155   {
HITCBC 156   4548 static recycling_memory_resource instance; 156   4555 static recycling_memory_resource instance;
HITCBC 157   4548 return &instance; 157   4555 return &instance;
158   } 158   }
159   159  
160   } // namespace capy 160   } // namespace capy
161   } // namespace boost 161   } // namespace boost