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