Skip to content

Commit 1dd43b2

Browse files
committed
Add size threshold to proxy lib to call system allocator (Linux only)
Add a size threshold to proxy lib to call system allocator when the size is less than the given threshold (Linux only yet). Signed-off-by: Lukasz Dorau <[email protected]>
1 parent bddbffd commit 1dd43b2

File tree

6 files changed

+198
-16
lines changed

6 files changed

+198
-16
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,13 @@ The memory used by the proxy memory allocator is mmap'ed:
317317
- `page.disposition=shared-shm` - IPC uses the named shared memory. An SHM name is generated using the `umf_proxy_lib_shm_pid_$PID` pattern, where `$PID` is the PID of the process. It creates the `/dev/shm/umf_proxy_lib_shm_pid_$PID` file.
318318
- `page.disposition=shared-fd` - IPC uses the file descriptor duplication. It requires using `pidfd_getfd(2)` to obtain a duplicate of another process's file descriptor. Permission to duplicate another process's file descriptor is governed by a ptrace access mode `PTRACE_MODE_ATTACH_REALCREDS` check (see `ptrace(2)`) that can be changed using the `/proc/sys/kernel/yama/ptrace_scope` interface. `pidfd_getfd(2)` is supported since Linux 5.6.
319319

320+
**Size threshold**
321+
322+
The **size threshold** feature (Linux only) causes that all allocations of size less than the given threshold value go to the default system allocator instead of the proxy library.
323+
It can be enabled by adding the `size.threshold=<value>` string to the `UMF_PROXY` environment variable (with `';'` as a separator), for example: `UMF_PROXY="page.disposition=shared-shm;size.threshold=64"`.
324+
325+
**Remark:** changing a size of allocation (using `realloc()` ) does not change the allocator (`realloc(malloc(threshold - 1), threshold + 1)` still belongs to the default system allocator and `realloc(malloc(threshold + 1), threshold - 1)` still belongs to the proxy library pool allocator).
326+
320327
#### Windows
321328

322329
In case of Windows it requires:

src/proxy_lib/proxy_lib.c

Lines changed: 143 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@
2727
* - _aligned_offset_recalloc()
2828
*/
2929

30+
#ifndef _WIN32
31+
#define _GNU_SOURCE // for RTLD_NEXT
32+
#include <dlfcn.h>
33+
#undef _GNU_SOURCE
34+
#endif /* _WIN32 */
35+
3036
#if (defined PROXY_LIB_USES_JEMALLOC_POOL)
3137
#include <umf/pools/pool_jemalloc.h>
3238
#define umfPoolManagerOps umfJemallocPoolOps
@@ -48,6 +54,7 @@
4854
#include "base_alloc_linear.h"
4955
#include "proxy_lib.h"
5056
#include "utils_common.h"
57+
#include "utils_load_library.h"
5158
#include "utils_log.h"
5259

5360
#ifdef _WIN32 /* Windows ***************************************/
@@ -94,6 +101,24 @@ void utils_init_once(UTIL_ONCE_FLAG *flag, void (*onceCb)(void));
94101
* of a UMF pool to allocate memory needed by an application. It should be freed
95102
* by an application.
96103
*/
104+
#ifndef _WIN32
105+
typedef void *(*system_aligned_alloc_t)(size_t alignment, size_t size);
106+
typedef void *(*system_calloc_t)(size_t nmemb, size_t size);
107+
typedef void (*system_free_t)(void *ptr);
108+
typedef void *(*system_malloc_t)(size_t size);
109+
typedef size_t (*system_malloc_usable_size_t)(void *ptr);
110+
typedef void *(*system_realloc_t)(void *ptr, size_t size);
111+
112+
// pointers to the default system allocator's API
113+
static system_aligned_alloc_t System_aligned_alloc;
114+
static system_calloc_t System_calloc;
115+
static system_free_t System_free;
116+
static system_malloc_t System_malloc;
117+
static system_malloc_usable_size_t System_malloc_usable_size;
118+
static system_realloc_t System_realloc;
119+
120+
static size_t Size_threshold_value = 0;
121+
#endif /* _WIN32 */
97122

98123
static UTIL_ONCE_FLAG Base_alloc_leak_initialized = UTIL_ONCE_FLAG_INIT;
99124
static umf_ba_linear_pool_t *Base_alloc_leak = NULL;
@@ -107,34 +132,87 @@ static __TLS int was_called_from_umfPool = 0;
107132
/*** The constructor and destructor of the proxy library *********************/
108133
/*****************************************************************************/
109134

135+
#ifndef _WIN32
136+
static size_t get_size_threshold(void) {
137+
char *str_threshold = utils_env_var_get_str("UMF_PROXY", "size.threshold=");
138+
LOG_DEBUG("UMF_PROXY[size.threshold] = %s", str_threshold);
139+
long threshold = utils_get_size_threshold(str_threshold);
140+
if (threshold < 0) {
141+
LOG_ERR("incorrect size threshold: %s", str_threshold);
142+
exit(-1);
143+
}
144+
145+
return (size_t)threshold;
146+
}
147+
148+
static int get_system_allocator_symbols(void) {
149+
*((void **)(&System_aligned_alloc)) =
150+
utils_get_symbol_addr(RTLD_NEXT, "aligned_alloc", NULL);
151+
*((void **)(&System_calloc)) =
152+
utils_get_symbol_addr(RTLD_NEXT, "calloc", NULL);
153+
*((void **)(&System_free)) = utils_get_symbol_addr(RTLD_NEXT, "free", NULL);
154+
*((void **)(&System_malloc)) =
155+
utils_get_symbol_addr(RTLD_NEXT, "malloc", NULL);
156+
*((void **)(&System_malloc_usable_size)) =
157+
utils_get_symbol_addr(RTLD_NEXT, "malloc_usable_size", NULL);
158+
*((void **)(&System_realloc)) =
159+
utils_get_symbol_addr(RTLD_NEXT, "realloc", NULL);
160+
161+
if (System_aligned_alloc && System_calloc && System_free && System_malloc &&
162+
System_malloc_usable_size && System_realloc) {
163+
return 0;
164+
}
165+
166+
*((void **)(&System_aligned_alloc)) = NULL;
167+
*((void **)(&System_calloc)) = NULL;
168+
*((void **)(&System_free)) = NULL;
169+
*((void **)(&System_malloc)) = NULL;
170+
*((void **)(&System_malloc_usable_size)) = NULL;
171+
*((void **)(&System_realloc)) = NULL;
172+
173+
return -1;
174+
}
175+
#endif /* _WIN32 */
176+
110177
void proxy_lib_create_common(void) {
111178
utils_log_init();
112179
umf_os_memory_provider_params_t os_params =
113180
umfOsMemoryProviderParamsDefault();
114181
umf_result_t umf_result;
115182

116183
#ifndef _WIN32
117-
char shm_name[NAME_MAX];
184+
size_t _threshold = get_size_threshold();
185+
if (_threshold > 0) {
186+
if (get_system_allocator_symbols()) {
187+
LOG_ERR("initialization of the system allocator failed!");
188+
exit(-1);
189+
}
190+
191+
Size_threshold_value = _threshold;
192+
LOG_INFO("system allocator initialized, size threshold value = %zu",
193+
Size_threshold_value);
194+
}
118195

119196
if (utils_env_var_has_str("UMF_PROXY", "page.disposition=shared-fd")) {
120-
LOG_DEBUG("proxy_lib: using the MAP_SHARED visibility mode with the "
121-
"file descriptor duplication");
197+
LOG_INFO("proxy_lib: using the MAP_SHARED visibility mode with the "
198+
"file descriptor duplication");
122199
os_params.visibility = UMF_MEM_MAP_SHARED;
123200
os_params.shm_name = NULL;
124201

125202
} else if (utils_env_var_has_str("UMF_PROXY",
126203
"page.disposition=shared-shm")) {
127204
os_params.visibility = UMF_MEM_MAP_SHARED;
128205

206+
char shm_name[NAME_MAX];
129207
memset(shm_name, 0, NAME_MAX);
130208
sprintf(shm_name, "umf_proxy_lib_shm_pid_%i", utils_getpid());
131209
os_params.shm_name = shm_name;
132210

133-
LOG_DEBUG("proxy_lib: using the MAP_SHARED visibility mode with the "
134-
"named shared memory: %s",
135-
os_params.shm_name);
211+
LOG_INFO("proxy_lib: using the MAP_SHARED visibility mode with the "
212+
"named shared memory: %s",
213+
os_params.shm_name);
136214
}
137-
#endif
215+
#endif /* _WIN32 */
138216

139217
umf_result = umfMemoryProviderCreate(umfOsMemoryProviderOps(), &os_params,
140218
&OS_memory_provider);
@@ -149,16 +227,18 @@ void proxy_lib_create_common(void) {
149227
LOG_ERR("creating UMF pool manager failed");
150228
exit(-1);
151229
}
230+
152231
// The UMF pool has just been created (Proxy_pool != NULL). Stop using
153232
// the linear allocator and start using the UMF pool allocator from now on.
233+
LOG_DEBUG("proxy library initialized");
154234
}
155235

156236
void proxy_lib_destroy_common(void) {
157237
if (utils_is_running_in_proxy_lib()) {
158238
// We cannot destroy 'Base_alloc_leak' nor 'Proxy_pool' nor 'OS_memory_provider',
159239
// because it could lead to use-after-free in the program's unloader
160240
// (for example _dl_fini() on Linux).
161-
return;
241+
goto fini_proxy_lib_destroy_common;
162242
}
163243

164244
umf_memory_pool_handle_t pool = Proxy_pool;
@@ -168,6 +248,10 @@ void proxy_lib_destroy_common(void) {
168248
umf_memory_provider_handle_t provider = OS_memory_provider;
169249
OS_memory_provider = NULL;
170250
umfMemoryProviderDestroy(provider);
251+
LOG_DEBUG("proxy library destroyed");
252+
253+
fini_proxy_lib_destroy_common:
254+
LOG_DEBUG("proxy library finalized");
171255
}
172256

173257
/*****************************************************************************/
@@ -246,6 +330,12 @@ static inline size_t ba_leak_pool_contains_pointer(void *ptr) {
246330
/*****************************************************************************/
247331

248332
void *malloc(size_t size) {
333+
#ifndef _WIN32
334+
if (size < Size_threshold_value) {
335+
return System_malloc(size);
336+
}
337+
#endif /* _WIN32 */
338+
249339
if (!was_called_from_umfPool && Proxy_pool) {
250340
was_called_from_umfPool = 1;
251341
void *ptr = umfPoolMalloc(Proxy_pool, size);
@@ -257,6 +347,12 @@ void *malloc(size_t size) {
257347
}
258348

259349
void *calloc(size_t nmemb, size_t size) {
350+
#ifndef _WIN32
351+
if ((nmemb * size) < Size_threshold_value) {
352+
return System_calloc(nmemb, size);
353+
}
354+
#endif /* _WIN32 */
355+
260356
if (!was_called_from_umfPool && Proxy_pool) {
261357
was_called_from_umfPool = 1;
262358
void *ptr = umfPoolCalloc(Proxy_pool, nmemb, size);
@@ -276,15 +372,22 @@ void free(void *ptr) {
276372
return;
277373
}
278374

279-
if (Proxy_pool) {
375+
if (Proxy_pool && (umfPoolByPtr(ptr) == Proxy_pool)) {
280376
if (umfPoolFree(Proxy_pool, ptr) != UMF_RESULT_SUCCESS) {
281377
LOG_ERR("umfPoolFree() failed");
282-
assert(0);
283378
}
284379
return;
285380
}
286381

287-
assert(0);
382+
#ifndef _WIN32
383+
if (Size_threshold_value) {
384+
System_free(ptr);
385+
return;
386+
}
387+
#endif /* _WIN32 */
388+
389+
LOG_ERR("free() failed: %p", ptr);
390+
288391
return;
289392
}
290393

@@ -303,18 +406,31 @@ void *realloc(void *ptr, size_t size) {
303406
return ba_leak_realloc(ptr, size, leak_pool_contains_pointer);
304407
}
305408

306-
if (Proxy_pool) {
409+
if (Proxy_pool && (umfPoolByPtr(ptr) == Proxy_pool)) {
307410
was_called_from_umfPool = 1;
308411
void *new_ptr = umfPoolRealloc(Proxy_pool, ptr, size);
309412
was_called_from_umfPool = 0;
310413
return new_ptr;
311414
}
312415

313-
assert(0);
416+
#ifndef _WIN32
417+
if (Size_threshold_value) {
418+
return System_realloc(ptr, size);
419+
}
420+
#endif /* _WIN32 */
421+
422+
LOG_ERR("realloc() failed: %p", ptr);
423+
314424
return NULL;
315425
}
316426

317427
void *aligned_alloc(size_t alignment, size_t size) {
428+
#ifndef _WIN32
429+
if (size < Size_threshold_value) {
430+
return System_aligned_alloc(alignment, size);
431+
}
432+
#endif /* _WIN32 */
433+
318434
if (!was_called_from_umfPool && Proxy_pool) {
319435
was_called_from_umfPool = 1;
320436
void *ptr = umfPoolAlignedMalloc(Proxy_pool, size, alignment);
@@ -330,19 +446,30 @@ size_t _msize(void *ptr) {
330446
#else
331447
size_t malloc_usable_size(void *ptr) {
332448
#endif
333-
334-
// a check to verify we are running the proxy library
449+
// a check to verify if we are running the proxy library
335450
if (ptr == (void *)0x01) {
336451
return 0xDEADBEEF;
337452
}
338453

339-
if (!was_called_from_umfPool && Proxy_pool) {
454+
if (ba_leak_pool_contains_pointer(ptr)) {
455+
return 0; // unsupported in case of the ba_leak allocator
456+
}
457+
458+
if (Proxy_pool && (umfPoolByPtr(ptr) == Proxy_pool)) {
340459
was_called_from_umfPool = 1;
341460
size_t size = umfPoolMallocUsableSize(Proxy_pool, ptr);
342461
was_called_from_umfPool = 0;
343462
return size;
344463
}
345464

465+
#ifndef _WIN32
466+
if (Size_threshold_value) {
467+
return System_malloc_usable_size(ptr);
468+
}
469+
#endif /* _WIN32 */
470+
471+
LOG_ERR("malloc_usable_size() failed: %p", ptr);
472+
346473
return 0; // unsupported in this case
347474
}
348475

src/utils/utils_common.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ int utils_file_open_or_create(const char *path);
168168

169169
int utils_fallocate(int fd, long offset, long len);
170170

171+
long utils_get_size_threshold(char *str_threshold);
172+
171173
#ifdef __cplusplus
172174
}
173175
#endif

src/utils/utils_posix_common.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*
88
*/
99

10+
#include <ctype.h>
1011
#include <errno.h>
1112
#include <fcntl.h>
1213
#include <limits.h>
@@ -266,3 +267,27 @@ int utils_file_open_or_create(const char *path) {
266267

267268
return fd;
268269
}
270+
271+
// Expected input:
272+
// char *str_threshold = utils_env_var_get_str("UMF_PROXY", "size.threshold=");
273+
long utils_get_size_threshold(char *str_threshold) {
274+
if (!str_threshold) {
275+
return 0;
276+
}
277+
278+
// move to the beginning of the number
279+
str_threshold += strlen("size.threshold=");
280+
281+
// check if the first character is a digit
282+
if (!isdigit(str_threshold[0])) {
283+
LOG_ERR("incorrect size threshold, expected numerical value >=0: %s",
284+
str_threshold);
285+
return -1;
286+
}
287+
288+
size_t threshold = atol(str_threshold);
289+
LOG_DEBUG("Size_threshold_value = (char *) %s, (size_t) %zu", str_threshold,
290+
threshold);
291+
292+
return threshold;
293+
}

src/utils/utils_windows_common.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,10 @@ int utils_fallocate(int fd, long offset, long len) {
215215

216216
return -1;
217217
}
218+
219+
// Expected input:
220+
// char *str_threshold = utils_env_var_get_str("UMF_PROXY", "size.threshold=");
221+
long utils_get_size_threshold(char *str_threshold) {
222+
(void)str_threshold; // unused
223+
return 0;
224+
}

test/utils/utils_linux.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,17 @@ TEST_F(test, utils_shm_create_invalid_args) {
5656
ret = utils_shm_create("/abc", -1);
5757
EXPECT_EQ(ret, -1);
5858
}
59+
60+
TEST_F(test, utils_get_size_threshold) {
61+
// Expected input to utils_get_size_threshold():
62+
// char *str_threshold = utils_env_var_get_str("UMF_PROXY", "size.threshold=");
63+
// positive tests
64+
EXPECT_EQ(utils_get_size_threshold((char *)"size.threshold=111"), 111);
65+
EXPECT_EQ(utils_get_size_threshold((char *)"size.threshold=222;abcd"), 222);
66+
EXPECT_EQ(utils_get_size_threshold((char *)"size.threshold=333;var=value"),
67+
333);
68+
// negative tests
69+
EXPECT_EQ(utils_get_size_threshold(NULL), 0);
70+
EXPECT_EQ(utils_get_size_threshold((char *)"size.threshold=abc"), -1);
71+
EXPECT_EQ(utils_get_size_threshold((char *)"size.threshold=-111"), -1);
72+
}

0 commit comments

Comments
 (0)