Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
memory_manager.cpp
Go to the documentation of this file.
1/* Flow-IPC: SHM-jemalloc
2 * Copyright (c) 2023 Akamai Technologies, Inc.; and other contributors.
3 * Each commit is copyright by its respective author or author's employer.
4 *
5 * Licensed under the MIT License:
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE. */
24
25/// @file
29#include "ipc/common.hpp"
30#include <flow/util/string_ostream.hpp>
31#include <flow/util/util.hpp>
32#include <boost/system/error_code.hpp>
33#include <cassert>
34#include <ostream>
35
37{
38
39// Implementations.
40
41void* Memory_manager::allocate(size_t size) const
42{
43 return IPC_SHM_ARENA_LEND_JEMALLOC_API(malloc)(size); // It returns null <=> we do (as advertised).
44}
45
46void* Memory_manager::allocate_no_tcache(size_t size, arena_id_t arena_id) const
47{
48 return allocate_helper(size, arena_id, MALLOCX_TCACHE_NONE);
49}
50
51void* Memory_manager::allocate(size_t size, arena_id_t arena_id, tcache_id_t thread_cache_id) const
52{
53 return allocate_helper(size, arena_id, MALLOCX_TCACHE(thread_cache_id));
54}
55
56void* Memory_manager::allocate_helper(size_t size, arena_id_t arena_id, int thread_cache_flags) const
57{
58 assert(size > 0);
59 // It returns null <=> we do (as advertised).
60 return IPC_SHM_ARENA_LEND_JEMALLOC_API(mallocx)(size, (MALLOCX_ARENA(arena_id) | thread_cache_flags));
61}
62
63void Memory_manager::deallocate(void* address) const noexcept
64{
66}
67
68void Memory_manager::deallocate_no_tcache(void* address) const noexcept
69{
70 deallocate_helper(address, MALLOCX_TCACHE_NONE);
71}
72
73void Memory_manager::deallocate(void* address, tcache_id_t thread_cache_id) const noexcept
74{
75 deallocate_helper(address, MALLOCX_TCACHE(thread_cache_id));
76}
77
78void Memory_manager::deallocate_helper(void* address, int thread_cache_flags) const noexcept
79{
80 assert(address);
81
82 IPC_SHM_ARENA_LEND_JEMALLOC_API(dallocx)(address, thread_cache_flags);
83}
84
85arena_id_t Memory_manager::create_arena(extent_hooks_t* extent_hooks) const
86{
87 using flow::util::Lock_guard;
88 using flow::util::Mutex_non_recursive;
89 using flow::error::Runtime_error;
90 using boost::system::system_category;
91
92 arena_id_t arena_id;
93 size_t output_size = sizeof(arena_id);
94 extent_hooks_t** input_param;
95 size_t input_size;
96 if (extent_hooks)
97 {
98 input_param = &extent_hooks;
99 input_size = sizeof(extent_hooks);
100 }
101 else
102 {
103 input_param = nullptr;
104 input_size = 0;
105 }
106
107 int ec;
108 {
109 /* `arenas.create` mutates the jemalloc arena set, so serialize it against any concurrent
110 * whole-arena-set stats dump (and against arena-create/destroy). See detail::jemalloc_arena_list_mutex() doc
111 * header for the whole story. Do note that if some other code does arenas.create et al directly, then this
112 * won't beat it; but if they use our nice API here then all good. */
113 Lock_guard<Mutex_non_recursive> lock{detail::jemalloc_arena_list_mutex()};
114 ec = IPC_SHM_ARENA_LEND_JEMALLOC_API(mallctl)("arenas.create", &arena_id, &output_size, input_param, input_size);
115 }
116 if (ec != 0)
117 {
118 throw Runtime_error{Error_code{ec, system_category()}, "je_mallctl() error on creating arena"};
119 }
120
121 return arena_id;
122} // Memory_manager::create_arena()
123
125{
126 destroy_arena(arena_id, log_ctx, [](auto&&, auto&& destroy_arena_func)
127 {
128 destroy_arena_func();
129 });
130 // Incidentally: that overload is aware of the detail::jemalloc_arena_list_mutex() issue and will do the right thing.
131}
132
133void Memory_manager::stats_dump_to_ostream(std::ostream& os, Stat_format format, util::String_view extra_opts) const
134{
135 using flow::util::Lock_guard;
136 using flow::util::Mutex_non_recursive;
137 using std::string;
138
139 /* jemalloc's malloc_stats_print() drives output via a write-callback invoked repeatedly with NUL-terminated
140 * chunks; route those straight to `os`. opts: null => human-readable text; "J" => JSON; plus any caller
141 * section flags appended via extra_opts. */
142 string opts{extra_opts};
143 if (format == Stat_format::S_JSON)
144 {
145 opts += 'J';
146 }
147
148 {
149 /* malloc_stats_print() refreshes jemalloc's stats and then reads many values; so firstly serialize it against
150 * Flow-IPC's other stats refresh-then-read sequences, lest they tear each other. See
151 * detail::jemalloc_stats_mutex() doc header.
152 *
153 * Secondly: malloc_stats_print() walks the jemalloc arena set unguarded, so serialize it against any concurrent
154 * arena-create and arena-destroy (which mutate that set). See detail::jemalloc_arena_list_mutex() doc header
155 * for the whole story. Do note that if some other code does arenas.create et al directly, then this
156 * won't beat it; but if they use our nice APIs then all good. (Lock order: stats mutex, then this leaf.) */
157 Lock_guard<Mutex_non_recursive> stats_lock{detail::jemalloc_stats_mutex()};
158 Lock_guard<Mutex_non_recursive> lock{detail::jemalloc_arena_list_mutex()};
159 IPC_SHM_ARENA_LEND_JEMALLOC_API(malloc_stats_print)
160 ([](void* cbopaque, const char* chunk)
161 { *(static_cast<std::ostream*>(cbopaque)) << chunk; },
162 // (^-- Capture-less lambda => function pointer conversion. Use their cbopaque dealio for access to `os`.)
163 &os, opts.empty() ? nullptr : opts.c_str());
164 }
165
166 if (format == Stat_format::S_JSON)
167 {
168 // The JSON is very compact (no white-space... and no newline); but our contract promises a trailing newline.
169 os << '\n';
170 }
171}
172
174{
175 flow::util::String_ostream os;
176 stats_dump_to_ostream(os.os(), format, extra_opts);
177 os.os() << std::flush;
178 return os.str();
179}
180
181void Memory_manager::log_stats_dump(flow::log::Logger* logger_ptr, Stat_format format,
182 flow::log::Sev sev, util::String_view extra_opts) const
183{
184 FLOW_LOG_SET_CONTEXT(logger_ptr, Log_component::S_SHM);
185 /* Single (multi-line) message; the dump is built (and run) only if `sev` passes the filter.
186 * With or without J[SON] option it ends in newline, so let's not add more in that spot. */
187 FLOW_LOG_WITH_CHECKING(sev,
188 "jemalloc stats dump (knobs: "
189 "[as-" << ((format == Stat_format::S_JSON) ? "JSON" : "text") << ", extra-opts"
190 "[" << extra_opts << "]]): "
191 "STATS_DUMP[[[\n" << stats_dump_to_string(format, extra_opts) << "]]]STATS_DUMP.");
192
193 /* @todo Not a big deal, at least not yet, but it'd be nice to provide `ostream << Memory_manager::Stat_format`
194 * as well as istream>> (using flow::util::istream_to_enum()). Could then just output `format` above and
195 * elsewhere; and it could be used in flow.cfg (and/or naked program_options and similar) options/settings
196 * directly. Its being an inner-class type would complicate putting those things in _fwd.hpp, so perhaps
197 * it would also become jemalloc::Memory_manager_stat_format... though that would be a breaking change; so
198 * maybe just forget about _fwd.hpp (an exception).
199 *
200 * Its use currently is so limited, that it seemed like overkill, and that may well remain the case. */
201}
202
203} // namespace ipc::shm::arena_lend::jemalloc
204
206{
207
208// Implementations.
209
210flow::util::Mutex_non_recursive& jemalloc_arena_list_mutex()
211{
212 using flow::util::Mutex_non_recursive;
213
214 // Immortal per our doc header.
215 static const auto s_mutex = new Mutex_non_recursive;
216 return *s_mutex;
217}
218
219flow::util::Mutex_non_recursive& jemalloc_stats_mutex()
220{
221 using flow::util::Mutex_non_recursive;
222
223 // Immortal per our doc header.
224 static const auto s_mutex = new Mutex_non_recursive;
225 return *s_mutex;
226}
227
228} // namespace ipc::shm::arena_lend::jemalloc::detail
arena_id_t create_arena(extent_hooks_t *extent_hooks) const
Creates a new segregated memory area (jemalloc-arena), wired to the given extent hooks (if any).
void deallocate_helper(void *address, int thread_cache_flags) const noexcept
Boring helper for deallocate_no_tcache() and deallocate(address, tcache_id).
void stats_dump_to_ostream(std::ostream &os=std::cout, Stat_format format=Stat_format::S_TEXT, util::String_view extra_opts={}) const
Writes a broad, raw, process-wide jemalloc statistics dump (all arenas) to os.
std::string stats_dump_to_string(Stat_format format=Stat_format::S_TEXT, util::String_view extra_opts={}) const
Like stats_dump_to_ostream() but returns the dump as a string.
void deallocate(void *address) const noexcept
Deallocates memory by using jemalloc's vanilla free() implementation.
void * allocate_no_tcache(size_t size, arena_id_t arena_id) const
Allocates uninitialized memory, in the specified jemalloc-arena, without the use of a thread cache.
void destroy_arena(arena_id_t arena_id, const flow::log::Log_context_mt *log_ctx, On_done_func &&on_done_func) const
Forwards to Thread_cache::destroy_arena_safely().
void deallocate_no_tcache(void *address) const noexcept
Deallocates memory similarly to deallocate(address), except that this is done without the use of a th...
void * allocate(size_t size) const
Allocates uninitialized memory by using jemalloc's vanilla malloc() implementation.
Stat_format
Output format for stats_dump_to_ostream() and the sibling stats-dump methods.
@ S_JSON
JSON (jemalloc malloc_stats_print() "J" option).
void * allocate_helper(size_t size, arena_id_t arena_id, int thread_cache_flags) const
Boring helper for allocate_no_tcache() and allocate(size, arena_id, tcache_id).
void log_stats_dump(flow::log::Logger *logger_ptr, Stat_format format=Stat_format::S_TEXT, flow::log::Sev sev=flow::log::Sev::S_INFO, util::String_view extra_opts={}) const
Like stats_dump_to_ostream() but logs the dump – as a single (multi-line) message – via logger_ptr at...
SHM-jemalloc internal (non-public) implementation details. See also stat-oriented sub-namespace: deta...
flow::util::Mutex_non_recursive & jemalloc_arena_list_mutex()
Returns the process-global mutex serializing any mutation of jemalloc's arena set (arena create/destr...
flow::util::Mutex_non_recursive & jemalloc_stats_mutex()
Returns the process-global mutex held throughout each of Flow-IPC's jemalloc-statistics operations – ...
ipc::shm sub-module with the SHM-jemalloc SHM-provider. See ipc::shm doc header for introduction.
unsigned int arena_id_t
Arena ID as dictated by jemalloc.
unsigned int tcache_id_t
Thread cache ID type as dictated by jemalloc.
flow::util::String_view String_view
Short-hand for Flow's String_view.
Definition: util_fwd.hpp:112
flow::Error_code Error_code
Short-hand for flow::Error_code which is very common.
Definition: common.hpp:313
#define IPC_SHM_ARENA_LEND_JEMALLOC_API(ARG_api_name)
To be used when invoking jemalloc public API functions, this evaluates to the given argument (which s...
Definition: jemalloc.hpp:87