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   14  
14   namespace boost { 15   namespace boost {
15   namespace capy { 16   namespace capy {
16   17  
17   // Instance destruction does nothing: the resource is stateless (all pools 18   // Instance destruction does nothing: the resource is stateless (all pools
18   // 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
19   // 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,
20   // independent of any instance lifetime. 21   // independent of any instance lifetime.
HITCBC 21   303 recycling_memory_resource::~recycling_memory_resource() = default; 22   306 recycling_memory_resource::~recycling_memory_resource() = default;
22   23  
23 - void 24 + #if (defined(BOOST_CAPY_DYN_LINK) || defined(BOOST_ALL_DYN_LINK)) && \
  25 + !defined(BOOST_CAPY_STATIC_LINK)
  26 +
  27 + recycling_memory_resource::pool&
HITGNC   28 + 643788 recycling_memory_resource::local() noexcept
  29 + {
  30 + static thread_local pool p;
HITGNC   31 + 643788 return p;
  32 + }
  33 +
  34 + bool&
HITGNC   35 + 216968 recycling_memory_resource::thread_armed() noexcept
  36 + {
  37 + static thread_local bool armed = false;
HITGNC   38 + 216968 return armed;
  39 + }
  40 +
  41 + #endif
  42 +
  43 + namespace {
  44 +
  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.
  47 + thread_local bool pool_drained = false;
  48 +
  49 + } // namespace
  50 +
  51 + bool
HITCBC 24   7204 recycling_memory_resource::arm_thread_cleanup() noexcept 52   210581 recycling_memory_resource::arm_thread_cleanup() noexcept
25   { 53   {
26   struct janitor 54   struct janitor
27   { 55   {
HITCBC 28   289 ~janitor() 56   364 ~janitor()
29   { 57   {
30   // Return this thread's cached blocks to the OS so nothing 58   // Return this thread's cached blocks to the OS so nothing
31   // 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
32   // thread-local storage is still valid here. 60   // thread-local storage is still valid here.
HITCBC 33   289 auto& lp = local(); 61   364 auto& lp = local();
HITCBC 34   2023 for(auto& b : lp.buckets) 62   2548 for(auto& b : lp.buckets)
HITCBC 35   5986 while(b.count > 0) 63   6719 while(b.count > 0)
HITCBC 36   4252 ::operator delete(b.pop()); 64   4535 ::operator delete(b.pop());
HITGNC   65 + 364 pool_drained = true;
HITGNC   66 + 364 thread_armed() = false;
HITCBC 37   289 } 67   364 }
38   }; 68   };
HITGNC   69 + 210581 if(pool_drained)
HITGNC   70 + 5 return false;
HITCBC 39   7204 static thread_local janitor j; 71   210576 static thread_local janitor j;
HITGIC 40 - (void)j; 72 + 210576 std::ignore = j;
HITGNC   73 + 210576 return true;
ECB 41   7204 } 74   }
42   75  
43   recycling_memory_resource::pool& 76   recycling_memory_resource::pool&
HITCBC 44   13249 recycling_memory_resource::global() noexcept 77   419751 recycling_memory_resource::global() noexcept
45   { 78   {
46   // 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
47   // itself must stay guard-free for local()). Runs unconditionally at 80   // itself must stay guard-free for local()). Runs unconditionally at
48   // process exit, mirroring the original global pool destructor, and is 81   // process exit, mirroring the original global pool destructor, and is
49   // 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
50   // 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.
51   struct holder 84   struct holder
52   { 85   {
53   pool p; 86   pool p;
54   87  
HITCBC 55   27 ~holder() 88   33 ~holder()
56   { 89   {
HITCBC 57   27 std::lock_guard<std::mutex> lock(global_mutex()); 90   33 std::lock_guard<std::mutex> lock(global_mutex());
HITCBC 58   189 for(auto& b : p.buckets) 91   231 for(auto& b : p.buckets)
HITCBC 59   203 while(b.count > 0) 92   321 while(b.count > 0)
HITCBC 60   41 ::operator delete(b.pop()); 93   123 ::operator delete(b.pop());
HITCBC 61   27 } 94   33 }
62   }; 95   };
HITCBC 63   13249 static holder h; 96   419751 static holder h;
HITCBC 64   13249 return h.p; 97   419751 return h.p;
65   } 98   }
66   99  
67   std::mutex& 100   std::mutex&
HITCBC 68   13276 recycling_memory_resource::global_mutex() noexcept 101   419784 recycling_memory_resource::global_mutex() noexcept
69   { 102   {
70   // Never destroyed: it is locked during process-exit teardown (in 103   // Never destroyed: it is locked during process-exit teardown (in
71   // global()'s holder destructor), after a function-local `static 104   // global()'s holder destructor), after a function-local `static
72   // std::mutex` could itself have been destroyed. Placement-new into 105   // std::mutex` could itself have been destroyed. Placement-new into
73   // 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.
74   alignas(std::mutex) static unsigned char storage[sizeof(std::mutex)]; 107   alignas(std::mutex) static unsigned char storage[sizeof(std::mutex)];
75   static std::mutex* const mtx = 108   static std::mutex* const mtx =
HITCBC 76   13276 ::new(static_cast<void*>(storage)) std::mutex(); 109   419784 ::new(static_cast<void*>(storage)) std::mutex();
HITCBC 77   13276 return *mtx; 110   419784 return *mtx;
78   } 111   }
79   112  
80   void* 113   void*
HITCBC 81   6921 recycling_memory_resource::allocate_slow( 114   210221 recycling_memory_resource::allocate_slow(
82   std::size_t rounded, std::size_t idx) 115   std::size_t rounded, std::size_t idx)
83   { 116   {
ECB 84 - 6921 arm_thread_cleanup(); 117 + // Once this thread's pool was drained at exit, take one block and
  118 + // leave the pool empty: nothing would release a refill.
HITGNC   119 + 210221 bool const cache = arm_thread_cleanup();
85   { 120   {
HITCBC 86   6921 std::lock_guard<std::mutex> lock(global_mutex()); 121   210221 std::lock_guard<std::mutex> lock(global_mutex());
HITCBC 87 - 6921 if(auto* p = global().buckets[idx].pop(local().buckets[idx])) 122 + 210221 auto& b = global().buckets[idx];
HITGNC   123 + 210221 if(auto* p = cache ? b.pop(local().buckets[idx]) : b.pop())
HITCBC 88   523 return p; 124   390 return p;
HITCBC 89   6921 } 125   210221 }
HITCBC 90   6398 return ::operator new(rounded); 126   209831 return ::operator new(rounded);
91   } 127   }
92   128  
93   void 129   void
HITCBC 94   6328 recycling_memory_resource::deallocate_slow( 130   209530 recycling_memory_resource::deallocate_slow(
95   void* p, std::size_t idx) 131   void* p, std::size_t idx)
96   { 132   {
97   { 133   {
HITCBC 98   6328 std::lock_guard<std::mutex> lock(global_mutex()); 134   209530 std::lock_guard<std::mutex> lock(global_mutex());
HITCBC 99   6328 if(global().buckets[idx].push(p)) 135   209530 if(global().buckets[idx].push(p))
HITCBC 100   4223 return; 136   4357 return;
HITCBC 101   6328 } 137   209530 }
HITCBC 102   2105 ::operator delete(p); 138   205173 ::operator delete(p);
103   } 139   }
104   140  
105   void* 141   void*
HITCBC 106   1255 recycling_memory_resource::do_allocate(std::size_t bytes, std::size_t alignment) 142   102469 recycling_memory_resource::do_allocate(std::size_t bytes, std::size_t alignment)
107   { 143   {
HITCBC 108   1255 return allocate_fast(bytes, alignment); 144   102469 return allocate_fast(bytes, alignment);
109   } 145   }
110   146  
111   void 147   void
HITCBC 112   1255 recycling_memory_resource::do_deallocate(void* p, std::size_t bytes, std::size_t alignment) 148   102469 recycling_memory_resource::do_deallocate(void* p, std::size_t bytes, std::size_t alignment)
113   { 149   {
HITCBC 114   1255 deallocate_fast(p, bytes, alignment); 150   102469 deallocate_fast(p, bytes, alignment);
HITCBC 115   1255 } 151   102469 }
116   152  
117   std::pmr::memory_resource* 153   std::pmr::memory_resource*
HITCBC 118   1639 get_recycling_memory_resource() noexcept 154   4552 get_recycling_memory_resource() noexcept
119   { 155   {
HITCBC 120   1639 static recycling_memory_resource instance; 156   4552 static recycling_memory_resource instance;
HITCBC 121   1639 return &instance; 157   4552 return &instance;
122   } 158   }
123   159  
124   } // namespace capy 160   } // namespace capy
125   } // namespace boost 161   } // namespace boost