src/ex/recycling_memory_resource.cpp

100.0% Lines (43/43) 100.0% List of functions (11/11)
recycling_memory_resource.cpp
f(x) Functions (11)
Line TLA Hits 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
14 namespace boost {
15 namespace capy {
16
17 // Instance destruction does nothing: the resource is stateless (all pools
18 // 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 // independent of any instance lifetime.
21 306x recycling_memory_resource::~recycling_memory_resource() = default;
22
23 void
24 7088x recycling_memory_resource::arm_thread_cleanup() noexcept
25 {
26 struct janitor
27 {
28 297x ~janitor()
29 {
30 // Return this thread's cached blocks to the OS so nothing
31 // leaks at thread exit. The pool has a trivial dtor, so its
32 // thread-local storage is still valid here.
33 297x auto& lp = local();
34 2079x for(auto& b : lp.buckets)
35 6036x while(b.count > 0)
36 4254x ::operator delete(b.pop());
37 297x }
38 };
39 7088x static thread_local janitor j;
40 (void)j;
41 7088x }
42
43 recycling_memory_resource::pool&
44 13075x recycling_memory_resource::global() noexcept
45 {
46 // Holder gives the global pool a destructor (the trivial-dtor pool type
47 // itself must stay guard-free for local()). Runs unconditionally at
48 // 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
50 // is cold (slow paths only), so the holder's guard costs nothing hot.
51 struct holder
52 {
53 pool p;
54
55 35x ~holder()
56 {
57 35x std::lock_guard<std::mutex> lock(global_mutex());
58 245x for(auto& b : p.buckets)
59 251x while(b.count > 0)
60 41x ::operator delete(b.pop());
61 35x }
62 };
63 13075x static holder h;
64 13075x return h.p;
65 }
66
67 std::mutex&
68 13110x recycling_memory_resource::global_mutex() noexcept
69 {
70 // Never destroyed: it is locked during process-exit teardown (in
71 // global()'s holder destructor), after a function-local `static
72 // std::mutex` could itself have been destroyed. Placement-new into
73 // static storage owns no heap allocation, so there is nothing to leak.
74 alignas(std::mutex) static unsigned char storage[sizeof(std::mutex)];
75 static std::mutex* const mtx =
76 13110x ::new(static_cast<void*>(storage)) std::mutex();
77 13110x return *mtx;
78 }
79
80 void*
81 6797x recycling_memory_resource::allocate_slow(
82 std::size_t rounded, std::size_t idx)
83 {
84 6797x arm_thread_cleanup();
85 {
86 6797x std::lock_guard<std::mutex> lock(global_mutex());
87 6797x if(auto* p = global().buckets[idx].pop(local().buckets[idx]))
88 547x return p;
89 6797x }
90 6250x return ::operator new(rounded);
91 }
92
93 void
94 6278x recycling_memory_resource::deallocate_slow(
95 void* p, std::size_t idx)
96 {
97 {
98 6278x std::lock_guard<std::mutex> lock(global_mutex());
99 6278x if(global().buckets[idx].push(p))
100 4323x return;
101 6278x }
102 1955x ::operator delete(p);
103 }
104
105 void*
106 2639x recycling_memory_resource::do_allocate(std::size_t bytes, std::size_t alignment)
107 {
108 2639x return allocate_fast(bytes, alignment);
109 }
110
111 void
112 2639x recycling_memory_resource::do_deallocate(void* p, std::size_t bytes, std::size_t alignment)
113 {
114 2639x deallocate_fast(p, bytes, alignment);
115 2639x }
116
117 std::pmr::memory_resource*
118 2908x get_recycling_memory_resource() noexcept
119 {
120 2908x static recycling_memory_resource instance;
121 2908x return &instance;
122 }
123
124 } // namespace capy
125 } // namespace boost
126