Skip to content

Commit eb03fad

Browse files
author
Daniel Blanchard
committed
WL#14633: Add support for jemalloc on Windows
Use jemalloc.dll (if present) for my_malloc in my_sys.lib and for ut::malloc etc in innobase.lib via function pointers. The function pointers are initialized to std::malloc, std::free etc so if jemalloc.dll is not present, the standard memory management functions are used. The use of the jemalloc functions is rendered "off by default" by the presence of a use_jemalloc_allocations function in mysys.lib that returns false. Clients of mysys.lib and innobase.lib wishing to use jemalloc (currently only the MySQL server executable and unit tests) enable it by providing an "overriding" definition of the use_jemalloc_allocations function that returns true. RB: 26872 Change-Id: If2e12d2e8504cb63c6ea0147832e2bb07ed13cb4
1 parent e1dd314 commit eb03fad

File tree

12 files changed

+471
-23
lines changed

12 files changed

+471
-23
lines changed

include/jemalloc_win.h

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*****************************************************************************
2+
3+
Copyright (c) 2021, Oracle and/or its affiliates.
4+
5+
This program is free software; you can redistribute it and/or modify it under
6+
the terms of the GNU General Public License, version 2.0, as published by the
7+
Free Software Foundation.
8+
9+
This program is also distributed with certain software (including but not
10+
limited to OpenSSL) that is licensed under separate terms, as designated in a
11+
particular file or component or in included license documentation. The authors
12+
of MySQL hereby grant you an additional permission to link the program and
13+
your derivative works with the separately licensed software that they have
14+
included with MySQL.
15+
16+
This program is distributed in the hope that it will be useful, but WITHOUT
17+
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18+
FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0,
19+
for more details.
20+
21+
You should have received a copy of the GNU General Public License along with
22+
this program; if not, write to the Free Software Foundation, Inc.,
23+
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24+
25+
*****************************************************************************/
26+
#ifndef JEMALLOC_WIN_INCLUDED
27+
#define JEMALLOC_WIN_INCLUDED
28+
29+
/** @file jemalloc_win.h
30+
Details for dynamically loading and using jemalloc.dll on Windows */
31+
32+
#ifdef _WIN32
33+
#include <mutex>
34+
#include <string>
35+
#include <vector>
36+
#include "my_loglevel.h"
37+
const constexpr char *jemalloc_dll_name = "jemalloc.dll";
38+
const constexpr char *jemalloc_malloc_function_name = "je_malloc";
39+
const constexpr char *jemalloc_calloc_function_name = "je_calloc";
40+
const constexpr char *jemalloc_realloc_function_name = "je_realloc";
41+
const constexpr char *jemalloc_free_function_name = "je_free";
42+
43+
namespace mysys {
44+
extern bool is_my_malloc_using_jemalloc();
45+
struct LogMessageInfo {
46+
loglevel m_severity;
47+
int64_t m_ecode;
48+
std::string m_message;
49+
};
50+
extern std::vector<LogMessageInfo> fetch_jemalloc_initialization_messages();
51+
52+
const int64_t MY_MALLOC_USING_JEMALLOC_ER = 0;
53+
const int64_t MY_MALLOC_USING_STD_MALLOC_ER = 1;
54+
const int64_t MY_MALLOC_LOADLIBRARY_FAILED_ER = 2;
55+
const int64_t MY_MALLOC_GETPROCADDRESS_FAILED_ER = 3;
56+
57+
namespace detail {
58+
extern void *(*pfn_malloc)(size_t size);
59+
extern void *(*pfn_calloc)(size_t number, size_t size);
60+
extern void *(*pfn_realloc)(void *ptr, size_t size);
61+
extern void (*pfn_free)(void *ptr);
62+
extern std::once_flag init_malloc_pointers_flag;
63+
void init_malloc_pointers();
64+
} // namespace detail
65+
} // namespace mysys
66+
67+
#endif // _WIN32
68+
69+
#endif // JEMALLOC_WIN_INCLUDED

mysys/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ IF (WIN32)
150150
my_winerr.cc
151151
my_winfile.cc
152152
win_timers.cc
153+
my_nojemalloc.cc
153154
)
154155
ENDIF()
155156

mysys/my_malloc.cc

+206-4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
#include <string.h>
3535
#include <sys/types.h>
3636

37+
#ifdef _WIN32
38+
#include "jemalloc_win.h"
39+
#endif // _WIN32
40+
3741
#include "memory_debugging.h"
3842
#include "my_compiler.h"
3943
#include "my_dbug.h"
@@ -56,6 +60,192 @@ static void *my_raw_malloc(size_t size, myf my_flags);
5660
static void my_raw_free(void *ptr);
5761
extern void my_free(void *ptr);
5862

63+
#ifdef _WIN32
64+
#include <memory>
65+
#include <mutex>
66+
#include <sstream>
67+
#include <string>
68+
69+
extern "C" bool use_jemalloc_allocations();
70+
namespace mysys {
71+
namespace detail {
72+
void *(*pfn_malloc)(size_t size);
73+
void *(*pfn_calloc)(size_t number, size_t size);
74+
void *(*pfn_realloc)(void *ptr, size_t size);
75+
void (*pfn_free)(void *ptr);
76+
} // namespace detail
77+
} // namespace mysys
78+
79+
static std::string win_error_to_string(DWORD err) {
80+
struct local_freer {
81+
void operator()(LPTSTR ptr) { LocalFree(ptr); }
82+
};
83+
LPTSTR pmsg = nullptr;
84+
if (!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
85+
FORMAT_MESSAGE_IGNORE_INSERTS |
86+
FORMAT_MESSAGE_ALLOCATE_BUFFER,
87+
nullptr, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
88+
(LPTSTR)&pmsg, 0, nullptr)) {
89+
return "";
90+
}
91+
std::unique_ptr<TCHAR, local_freer> scope_guard{pmsg};
92+
std::string message(pmsg);
93+
// Remove any trailing CRLF
94+
if (message.rfind("\r\n") == (message.length() - 2)) {
95+
message.resize(message.length() - 2);
96+
}
97+
return message;
98+
}
99+
100+
namespace mysys {
101+
static bool using_jemalloc = false;
102+
bool is_my_malloc_using_jemalloc() { return using_jemalloc; }
103+
// This unwieldy add_or_return_messages function is a workaround for the fact
104+
// that we can't control the order of global initializers, and the server
105+
// logger is not available at the time the jemalloc allocation
106+
// functions are loaded. We can't split the add_or_return_messages function
107+
// into separate add_messages and return_messages functions with the
108+
// messages stored in a file scoped static vector, as messages stored in
109+
// that vector during the initialization of _another_ file scoped static
110+
// object could be lost when a file scoped static vector is itself
111+
// subsequently initialized.
112+
// So message codes and strings are stored in a static function scoped vector
113+
// for subsequent retrieval when the server logger IS available. This static
114+
// function scoped vector is immune to the order of initialization of file
115+
// scoped variables.
116+
// Note also that we can't use error codes from mysqld_error.h
117+
// here, as the mysys library is used in the generation of those
118+
// error codes. To avoid a circular dependency, the codes stored
119+
// in the LogMessageInfo::m_ecode are offsets to be applied to
120+
// the base error code ER_MY_MALLOC_USING_JEMALLOC to find the
121+
// "real" error code.
122+
static void add_or_return_messages(
123+
const LogMessageInfo &msg_info,
124+
std::vector<LogMessageInfo> *pvec = nullptr) {
125+
static std::vector<LogMessageInfo> vec_messages;
126+
127+
if (pvec) {
128+
*pvec = vec_messages;
129+
return;
130+
}
131+
vec_messages.emplace_back(msg_info);
132+
}
133+
134+
static void log_err_deferred(loglevel severity, int64_t ecode,
135+
const std::string &message) {
136+
add_or_return_messages({severity, ecode, message});
137+
}
138+
139+
std::vector<LogMessageInfo> fetch_jemalloc_initialization_messages() {
140+
std::vector<LogMessageInfo> jemalloc_init_messages;
141+
LogMessageInfo dummy;
142+
add_or_return_messages(dummy, &jemalloc_init_messages);
143+
return jemalloc_init_messages;
144+
}
145+
template <class T>
146+
static T get_proc_address(const char *function_name, HMODULE hlib,
147+
const char *dll_path) {
148+
auto tmp_pfn = reinterpret_cast<T>(GetProcAddress(hlib, function_name));
149+
if (!tmp_pfn) {
150+
DWORD err = GetLastError();
151+
std::ostringstream os;
152+
os << "GetProcAddress failed for \"" << function_name << "\" from \""
153+
<< dll_path << "\" with error code " << err << ": \""
154+
<< win_error_to_string(err) << "\" Falling back to default malloc";
155+
156+
log_err_deferred(ERROR_LEVEL, MY_MALLOC_GETPROCADDRESS_FAILED_ER, os.str());
157+
}
158+
return tmp_pfn;
159+
}
160+
namespace detail {
161+
std::once_flag init_malloc_pointers_flag;
162+
/**
163+
Initialize (Windows only) function pointers used for some memory allocations,
164+
including the my_malloc family. Note that these memory allocations can occur
165+
early in the process lifecycle (during construction of global objects) and
166+
thus init_malloc_pointers is called using std::call_once from the functions
167+
that wrap memory allocations rather than from my_win_init, where it would be
168+
too late.
169+
Note that as this function is invoked very early in the lifetime of the
170+
process (typically before main), the logging system is not yet initialized
171+
so calls to my_message_local send their output to stderr.
172+
Also note that when used from the MySQL server on Windows, there are
173+
typically two instances of the MySQL server process launched (see the
174+
--no-monitor option for launching a single instance) and thus the information
175+
message emitted on not finding the jemalloc dll will typically appear on
176+
stderr twice.
177+
*/
178+
void init_malloc_pointers() {
179+
// Set up to use the MSVC library routines by default.
180+
pfn_malloc = std::malloc;
181+
pfn_calloc = std::calloc;
182+
pfn_realloc = std::realloc;
183+
pfn_free = std::free;
184+
if (!use_jemalloc_allocations()) {
185+
return;
186+
}
187+
// If we can find the jemalloc library in the same directory as this
188+
// executable, and load all the required functions successfully, use the
189+
// jemalloc routines instead.
190+
// Note that the handle returned by LoadLibraryEx is deliberately NOT
191+
// released with a FreeLibrary call, as the functions used from this
192+
// library can be called from global object destructors late in the
193+
// process lifecycle. Not calling FreeLibrary is benign is this case.
194+
HMODULE hlib = LoadLibraryEx(jemalloc_dll_name, NULL,
195+
LOAD_LIBRARY_SEARCH_APPLICATION_DIR);
196+
if (NULL == hlib) {
197+
DWORD err = GetLastError();
198+
if (ERROR_MOD_NOT_FOUND == err) {
199+
// Normal behaviour: not finding the jemalloc dll means
200+
// we don't want to use it.
201+
std::ostringstream os;
202+
os << jemalloc_dll_name << " not found. Falling back to default malloc";
203+
log_err_deferred(INFORMATION_LEVEL, MY_MALLOC_USING_STD_MALLOC_ER,
204+
os.str());
205+
return;
206+
}
207+
208+
std::ostringstream os;
209+
os << "LoadLibraryEx(\"" << jemalloc_dll_name
210+
<< "\", NULL, LOAD_LIBRARY_SEARCH_APPLICATION_DIR) failed with error "
211+
"code "
212+
<< err << ": \"" << win_error_to_string(err)
213+
<< "\", using default malloc";
214+
215+
log_err_deferred(ERROR_LEVEL, MY_MALLOC_LOADLIBRARY_FAILED_ER, os.str());
216+
return;
217+
}
218+
219+
auto tmp_je_malloc = get_proc_address<decltype(pfn_malloc)>(
220+
jemalloc_malloc_function_name, hlib, jemalloc_dll_name);
221+
auto tmp_je_calloc = get_proc_address<decltype(pfn_calloc)>(
222+
jemalloc_calloc_function_name, hlib, jemalloc_dll_name);
223+
auto tmp_je_realloc = get_proc_address<decltype(pfn_realloc)>(
224+
jemalloc_realloc_function_name, hlib, jemalloc_dll_name);
225+
auto tmp_je_free = get_proc_address<decltype(pfn_free)>(
226+
jemalloc_free_function_name, hlib, jemalloc_dll_name);
227+
228+
if (!tmp_je_malloc || !tmp_je_calloc || !tmp_je_realloc || !tmp_je_free) {
229+
return;
230+
}
231+
232+
pfn_malloc = tmp_je_malloc;
233+
pfn_calloc = tmp_je_calloc;
234+
pfn_realloc = tmp_je_realloc;
235+
pfn_free = tmp_je_free;
236+
using_jemalloc = true;
237+
return;
238+
}
239+
} // namespace detail
240+
} // namespace mysys
241+
242+
#define malloc(size) mysys::detail::pfn_malloc((size))
243+
#define calloc(count, size) mysys::detail::pfn_calloc((count), (size))
244+
#define realloc(p, size) mysys::detail::pfn_realloc((p), (size))
245+
#define free(p) mysys::detail::pfn_free((p))
246+
247+
#endif // _WIN32
248+
59249
#ifdef USE_MALLOC_WRAPPER
60250

61251
void *my_malloc(PSI_memory_key key, size_t size, myf flags) {
@@ -182,11 +372,15 @@ static void *my_raw_malloc(size_t size, myf my_flags) {
182372
else
183373
point = _malloc_dbg(size, _CLIENT_BLOCK, __FILE__, __LINE__);
184374
#else
375+
#ifdef _WIN32
376+
std::call_once(mysys::detail::init_malloc_pointers_flag,
377+
mysys::detail::init_malloc_pointers);
378+
#endif // _WIN32
185379
if (my_flags & MY_ZEROFILL)
186380
point = calloc(size, 1);
187381
else
188382
point = malloc(size);
189-
#endif
383+
#endif // MY_MSCRT_DEBUG
190384

191385
DBUG_EXECUTE_IF("simulate_out_of_memory", {
192386
free(point);
@@ -236,8 +430,12 @@ static void *my_raw_realloc(void *oldpoint, size_t size, myf my_flags) {
236430
#if defined(MY_MSCRT_DEBUG)
237431
point = _realloc_dbg(oldpoint, size, _CLIENT_BLOCK, __FILE__, __LINE__);
238432
#else
433+
#ifdef _WIN32
434+
std::call_once(mysys::detail::init_malloc_pointers_flag,
435+
mysys::detail::init_malloc_pointers);
436+
#endif // _WIN32
239437
point = realloc(oldpoint, size);
240-
#endif
438+
#endif // MY_MSCRT_DEBUG
241439
#ifndef NDEBUG
242440
end:
243441
#endif
@@ -253,7 +451,7 @@ static void *my_raw_realloc(void *oldpoint, size_t size, myf my_flags) {
253451
DBUG_PRINT("exit", ("ptr: %p", point));
254452
return point;
255453
}
256-
#endif
454+
#endif // !USE_MALLOC_WRAPPER
257455

258456
/**
259457
Free memory allocated with my_raw_malloc.
@@ -266,8 +464,12 @@ static void my_raw_free(void *ptr) {
266464
#if defined(MY_MSCRT_DEBUG)
267465
_free_dbg(ptr, _CLIENT_BLOCK);
268466
#else
467+
#ifdef _WIN32
468+
std::call_once(mysys::detail::init_malloc_pointers_flag,
469+
mysys::detail::init_malloc_pointers);
470+
#endif // _WIN32
269471
free(ptr);
270-
#endif
472+
#endif // MY_MSCRT_DEBUG
271473
}
272474

273475
void *my_memdup(PSI_memory_key key, const void *from, size_t length,

mysys/my_nojemalloc.cc

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*****************************************************************************
2+
3+
Copyright (c) 2021, Oracle and/or its affiliates.
4+
5+
This program is free software; you can redistribute it and/or modify it under
6+
the terms of the GNU General Public License, version 2.0, as published by the
7+
Free Software Foundation.
8+
9+
This program is also distributed with certain software (including but not
10+
limited to OpenSSL) that is licensed under separate terms, as designated in a
11+
particular file or component or in included license documentation. The authors
12+
of MySQL hereby grant you an additional permission to link the program and
13+
your derivative works with the separately licensed software that they have
14+
included with MySQL.
15+
16+
This program is distributed in the hope that it will be useful, but WITHOUT
17+
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18+
FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0,
19+
for more details.
20+
21+
You should have received a copy of the GNU General Public License along with
22+
this program; if not, write to the Free Software Foundation, Inc.,
23+
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24+
25+
*****************************************************************************/
26+
27+
#ifdef _WIN32
28+
/* In order to use jemalloc in the mysys library, the use_jemalloc_allocations
29+
function must return true.
30+
31+
This definition of the function (in the mysys library) returns false, so by
32+
default, the mysys library will not use jemalloc for memory allocation.
33+
34+
Note that the linker always uses a symbol defined in an .obj file in preference
35+
to the same symbol defined in a lib. Thus a user of the mysys library who wants
36+
the mysys library to use jemalloc must provide an overriding definition (i.e.
37+
link with an obj file definition) of the use_jemalloc_allocations function that
38+
returns true.
39+
*/
40+
extern "C" bool use_jemalloc_allocations() { return false; }
41+
#endif

share/messages_to_error_log.txt

+12
Original file line numberDiff line numberDiff line change
@@ -11617,6 +11617,18 @@ ER_PERSISTED_VARIABLES_DECRYPTION_FAILED
1161711617
ER_PERSISTED_VARIABLES_LACK_KEYRING_SUPPORT
1161811618
eng "Persisting SENSITIVE variables in encrypted form requires keyring component loaded through manifest file."
1161911619

11620+
ER_MY_MALLOC_USING_JEMALLOC
11621+
eng "Using jemalloc.dll for my_malloc and ut::malloc etc."
11622+
11623+
ER_MY_MALLOC_USING_STD_MALLOC
11624+
eng "%s."
11625+
11626+
ER_MY_MALLOC_LOADLIBRARY_FAILED
11627+
eng "%s."
11628+
11629+
ER_MY_MALLOC_GETPROCADDRESS_FAILED
11630+
eng "%s."
11631+
1162011632
# DO NOT add server-to-client messages here;
1162111633
# they go in messages_to_clients.txt
1162211634
# in the same directory as this file.

0 commit comments

Comments
 (0)