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 15:55:14 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         306 : 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          644075 : recycling_memory_resource::local() noexcept
      29                 : {
      30                 :     static thread_local pool p;
      31          644075 :     return p;
      32                 : }
      33                 : 
      34                 : bool&
      35          216938 : recycling_memory_resource::thread_armed() noexcept
      36                 : {
      37                 :     static thread_local bool armed = false;
      38          216938 :     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          210927 : recycling_memory_resource::arm_thread_cleanup() noexcept
      53                 : {
      54                 :     struct janitor
      55                 :     {
      56             363 :         ~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             363 :             auto& lp = local();
      62            2541 :             for(auto& b : lp.buckets)
      63            6713 :                 while(b.count > 0)
      64            4535 :                     ::operator delete(b.pop());
      65             363 :             pool_drained = true;
      66             363 :             thread_armed() = false;
      67             363 :         }
      68                 :     };
      69          210927 :     if(pool_drained)
      70               5 :         return false;
      71          210922 :     static thread_local janitor j;
      72          210922 :     std::ignore = j;
      73          210922 :     return true;
      74                 : }
      75                 : 
      76                 : recycling_memory_resource::pool&
      77          420143 : 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              33 :         ~holder()
      89                 :         {
      90              33 :             std::lock_guard<std::mutex> lock(global_mutex());
      91             231 :             for(auto& b : p.buckets)
      92             321 :                 while(b.count > 0)
      93             123 :                     ::operator delete(b.pop());
      94              33 :         }
      95                 :     };
      96          420143 :     static holder h;
      97          420143 :     return h.p;
      98                 : }
      99                 : 
     100                 : std::mutex&
     101          420176 : 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          420176 :         ::new(static_cast<void*>(storage)) std::mutex();
     110          420176 :     return *mtx;
     111                 : }
     112                 : 
     113                 : void*
     114          210567 : 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          210567 :     bool const cache = arm_thread_cleanup();
     120                 :     {
     121          210567 :         std::lock_guard<std::mutex> lock(global_mutex());
     122          210567 :         auto& b = global().buckets[idx];
     123          210567 :         if(auto* p = cache ? b.pop(local().buckets[idx]) : b.pop())
     124             568 :             return p;
     125          210567 :     }
     126          209999 :     return ::operator new(rounded);
     127                 : }
     128                 : 
     129                 : void
     130          209576 : recycling_memory_resource::deallocate_slow(
     131                 :     void* p, std::size_t idx)
     132                 : {
     133                 :     {
     134          209576 :         std::lock_guard<std::mutex> lock(global_mutex());
     135          209576 :         if(global().buckets[idx].push(p))
     136            4235 :             return;
     137          209576 :     }
     138          205341 :     ::operator delete(p);
     139                 : }
     140                 : 
     141                 : void*
     142          102469 : recycling_memory_resource::do_allocate(std::size_t bytes, std::size_t alignment)
     143                 : {
     144          102469 :     return allocate_fast(bytes, alignment);
     145                 : }
     146                 : 
     147                 : void
     148          102469 : recycling_memory_resource::do_deallocate(void* p, std::size_t bytes, std::size_t alignment)
     149                 : {
     150          102469 :     deallocate_fast(p, bytes, alignment);
     151          102469 : }
     152                 : 
     153                 : std::pmr::memory_resource*
     154            4551 : get_recycling_memory_resource() noexcept
     155                 : {
     156            4551 :     static recycling_memory_resource instance;
     157            4551 :     return &instance;
     158                 : }
     159                 : 
     160                 : } // namespace capy
     161                 : } // namespace boost
        

Generated by: LCOV version 2.3