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
|