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 297x recycling_memory_resource::~recycling_memory_resource() = default;
22
23 void
24 7051x recycling_memory_resource::arm_thread_cleanup() noexcept
25 {
26 struct janitor
27 {
28 288x ~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 288x auto& lp = local();
34 2016x for(auto& b : lp.buckets)
35 5966x while(b.count > 0)
36 4238x ::operator delete(b.pop());
37 288x }
38 };
39 7051x static thread_local janitor j;
40 (void)j;
41 7051x }
42
43 recycling_memory_resource::pool&
44 13096x 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 26x ~holder()
56 {
57 26x std::lock_guard<std::mutex> lock(global_mutex());
58 182x for(auto& b : p.buckets)
59 197x while(b.count > 0)
60 41x ::operator delete(b.pop());
61 26x }
62 };
63 13096x static holder h;
64 13096x return h.p;
65 }
66
67 std::mutex&
68 13122x 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 13122x ::new(static_cast<void*>(storage)) std::mutex();
77 13122x return *mtx;
78 }
79
80 void*
81 6769x recycling_memory_resource::allocate_slow(
82 std::size_t rounded, std::size_t idx)
83 {
84 6769x arm_thread_cleanup();
85 {
86 6769x std::lock_guard<std::mutex> lock(global_mutex());
87 6769x if(auto* p = global().buckets[idx].pop(local().buckets[idx]))
88 601x return p;
89 6769x }
90 6168x return ::operator new(rounded);
91 }
92
93 void
94 6327x recycling_memory_resource::deallocate_slow(
95 void* p, std::size_t idx)
96 {
97 {
98 6327x std::lock_guard<std::mutex> lock(global_mutex());
99 6327x if(global().buckets[idx].push(p))
100 4438x return;
101 6327x }
102 1889x ::operator delete(p);
103 }
104
105 void*
106 1127x recycling_memory_resource::do_allocate(std::size_t bytes, std::size_t alignment)
107 {
108 1127x return allocate_fast(bytes, alignment);
109 }
110
111 void
112 1127x recycling_memory_resource::do_deallocate(void* p, std::size_t bytes, std::size_t alignment)
113 {
114 1127x deallocate_fast(p, bytes, alignment);
115 1127x }
116
117 std::pmr::memory_resource*
118 1364x get_recycling_memory_resource() noexcept
119 {
120 1364x static recycling_memory_resource instance;
121 1364x return &instance;
122 }
123
124 } // namespace capy
125 } // namespace boost
126