LCOV - code coverage report
Current view: top level - /jenkins/workspace/boost-root/libs/capy/src/ex - recycling_memory_resource.cpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 100.0 % 53 53
Test Date: 2026-10-17 14:10:44 Functions: 92.9 % 14 13 1

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
       3                 : //
       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)
       6                 : //
       7                 : // Official repository: https://github.com/cppalliance/capy
       8                 : //
       9                 : 
      10                 : #include <boost/capy/ex/recycling_memory_resource.hpp>
      11                 : 
      12                 : #include <new>
      13                 : #include <tuple>
      14                 : 
      15                 : namespace boost {
      16                 : namespace capy {
      17                 : 
      18                 : // Instance destruction does nothing: the resource is stateless (all pools
      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,
      21                 : // independent of any instance lifetime.
      22 HIT         307 : recycling_memory_resource::~recycling_memory_resource() = default;
      23                 : 
      24                 : #if (defined(BOOST_CAPY_DYN_LINK) || defined(BOOST_ALL_DYN_LINK)) && \
      25                 :     !defined(BOOST_CAPY_STATIC_LINK)
      26                 : 
      27                 : recycling_memory_resource::pool&
      28          644349 : recycling_memory_resource::local() noexcept
      29                 : {
      30                 :     static thread_local pool p;
      31          644349 :     return p;
      32                 : }
      33                 : 
      34                 : bool&
      35          216943 : recycling_memory_resource::thread_armed() noexcept
      36                 : {
      37                 :     static thread_local bool armed = false;
      38          216943 :     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
      52          211195 : recycling_memory_resource::arm_thread_cleanup() noexcept
      53                 : {
      54                 :     struct janitor
      55                 :     {
      56             365 :         ~janitor()
      57                 :         {
      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
      60                 :             // thread-local storage is still valid here.
      61             365 :             auto& lp = local();
      62            2555 :             for(auto& b : lp.buckets)
      63            6728 :                 while(b.count > 0)
      64            4538 :                     ::operator delete(b.pop());
      65             365 :             pool_drained = true;
      66             365 :             thread_armed() = false;
      67             365 :         }
      68                 :     };
      69          211195 :     if(pool_drained)
      70               5 :         return false;
      71          211190 :     static thread_local janitor j;
      72          211190 :     std::ignore = j;
      73          211190 :     return true;
      74                 : }
      75                 : 
      76                 : recycling_memory_resource::pool&
      77          420498 : recycling_memory_resource::global() noexcept
      78                 : {
      79                 :     // Holder gives the global pool a destructor (the trivial-dtor pool type
      80                 :     // itself must stay guard-free for local()). Runs unconditionally at
      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
      83                 :     // is cold (slow paths only), so the holder's guard costs nothing hot.
      84                 :     struct holder
      85                 :     {
      86                 :         pool p;
      87                 : 
      88              34 :         ~holder()
      89                 :         {
      90              34 :             std::lock_guard<std::mutex> lock(global_mutex());
      91             238 :             for(auto& b : p.buckets)
      92             327 :                 while(b.count > 0)
      93             123 :                     ::operator delete(b.pop());
      94              34 :         }
      95                 :     };
      96          420498 :     static holder h;
      97          420498 :     return h.p;
      98                 : }
      99                 : 
     100                 : std::mutex&
     101          420532 : recycling_memory_resource::global_mutex() noexcept
     102                 : {
     103                 :     // Never destroyed: it is locked during process-exit teardown (in
     104                 :     // global()'s holder destructor), after a function-local `static
     105                 :     // std::mutex` could itself have been destroyed. Placement-new into
     106                 :     // static storage owns no heap allocation, so there is nothing to leak.
     107                 :     alignas(std::mutex) static unsigned char storage[sizeof(std::mutex)];
     108                 :     static std::mutex* const mtx =
     109          420532 :         ::new(static_cast<void*>(storage)) std::mutex();
     110          420532 :     return *mtx;
     111                 : }
     112                 : 
     113                 : void*
     114          210833 : recycling_memory_resource::allocate_slow(
     115                 :     std::size_t rounded, std::size_t idx)
     116                 : {
     117                 :     // Once this thread's pool was drained at exit, take one block and
     118                 :     // leave the pool empty: nothing would release a refill.
     119          210833 :     bool const cache = arm_thread_cleanup();
     120                 :     {
     121          210833 :         std::lock_guard<std::mutex> lock(global_mutex());
     122          210833 :         auto& b = global().buckets[idx];
     123          210833 :         if(auto* p = cache ? b.pop(local().buckets[idx]) : b.pop())
     124             716 :             return p;
     125          210833 :     }
     126          210117 :     return ::operator new(rounded);
     127                 : }
     128                 : 
     129                 : void
     130          209665 : recycling_memory_resource::deallocate_slow(
     131                 :     void* p, std::size_t idx)
     132                 : {
     133                 :     {
     134          209665 :         std::lock_guard<std::mutex> lock(global_mutex());
     135          209665 :         if(global().buckets[idx].push(p))
     136            4209 :             return;
     137          209665 :     }
     138          205456 :     ::operator delete(p);
     139                 : }
     140                 : 
     141                 : void*
     142          102470 : recycling_memory_resource::do_allocate(std::size_t bytes, std::size_t alignment)
     143                 : {
     144          102470 :     return allocate_fast(bytes, alignment);
     145                 : }
     146                 : 
     147                 : void
     148          102470 : recycling_memory_resource::do_deallocate(void* p, std::size_t bytes, std::size_t alignment)
     149                 : {
     150          102470 :     deallocate_fast(p, bytes, alignment);
     151          102470 : }
     152                 : 
     153                 : std::pmr::memory_resource*
     154            4555 : get_recycling_memory_resource() noexcept
     155                 : {
     156            4555 :     static recycling_memory_resource instance;
     157            4555 :     return &instance;
     158                 : }
     159                 : 
     160                 : } // namespace capy
     161                 : } // namespace boost
        

Generated by: LCOV version 2.3