2025-08-13 - Memory allocation in HAProxy 3.3 The vast majority of dynamic memory allocations are performed from pools. Pools are optimized to store pre-calibrated objects of the right size for a given usage, try to favor locality and hot objects as much as possible, and are heavily instrumented to detect and help debug a wide class of bugs including buffer overflows, use-after-free, etc. For objects of random sizes, or those used only at configuration time, pools are not suited, and the regular malloc/free family is available, in addition of a few others. The standard allocation calls are intercepted at the code level (#define) when the code is compiled with -DDEBUG_MEM_STATS. For this reason, these calls are redefined as macros in "bug.h", and one must not try to use the pointers to such functions, as this may break DEBUG_MEM_STATS. This provides fine-grained stats about allocation/free per line of source code using locally implemented counters that can be consulted by "debug dev memstats". The calls are categorized into one of "calloc", "free", "malloc", "realloc", "strdup", "p_alloc", "p_free", the latter two designating pools. Extra calls such as memalign() and similar are also intercepted and counted as malloc. Due to the nature of this replacement, DEBUG_MEM_STATS cannot see operations performed in libraries or dependencies. In addition to DEBUG_MEM_STATS, when haproxy is built with USE_MEMORY_PROFILING the standard functions are wrapped by new ones defined in "activity.c", which also hold counters by call place. These ones are able to trace activity in libraries because the functions check the return pointer to figure where the call was made. The approach is different and relies on a large hash table. The files, function names and line numbers are not know, but by passing the pointer to dladdr(), we can often resolve most of these symbols. These operations are consulted via "show profiling memory". It must first be enabled either in the global config "profiling.memory on" or the CLI using "set profiling memory on". Memory profiling can also track pool allocations and frees thanks to knowing the size of the element and knowing a place where to store it. Some future evolutions might consider making this possible as well for pure malloc/free too by leveraging malloc_usable_size() a bit more. Finally, 3.3 brought aligned allocations. These are made available via a new family of functions around ha_aligned_alloc() that simply map to either posix_memalign(), memalign() or _aligned_malloc() for CYGWIN, depending on which one is available. This latter one requires to pass the pointer to _aligned_free() instead of free(), so for this reason, all aligned allocations have to be released using ha_aligned_free(). Since this mostly happens on configuration elements, in practice it's not as inconvenient as it can sound. These functions are in reality macros handled in "bug.h" like the previous ones in order to deal with DEBUG_MEM_STATS. All "alloc" variants are reported in memstats as "malloc". All "zalloc" variants are reported in memstats as "calloc". The currently available allocators are the following: - void *ha_aligned_alloc(size_t align, size_t size) - void *ha_aligned_zalloc(size_t align, size_t size) Equivalent of malloc() but aligned to bytes. The alignment MUST be at least as large as one word and MUST be a power of two. The "zalloc" variant also zeroes the area on success. Both return NULL on failure. - void *ha_aligned_alloc_safe(size_t align, size_t size) - void *ha_aligned_zalloc_safe(size_t align, size_t size) Equivalent of malloc() but aligned to bytes. The alignment is automatically adjusted to the nearest larger power of two that is at least as large as a word. The "zalloc" variant also zeroes the area on success. Both return NULL on failure. - (type *)ha_aligned_alloc_typed(size_t count, type) (type *)ha_aligned_zalloc_typed(size_t count, type) This macro returns an area aligned to the required alignment for type , large enough for objects of this type, and the result is a pointer of this type. The goal is to ease allocation of known structures whose alignment is not necessarily known to the developer (and to avoid encouraging to hard-code alignment). The cast in return also provides a last-minute control in case a wrong type is mistakenly used due to a poor copy-paste or an extra "*" after the type. When DEBUG_MEM_STATS is in use, the type is stored as a string in the ".extra" field so that it can be displayed in "debug dev memstats". The "zalloc" variant also zeroes the area on success. Both return NULL on failure. - void ha_aligned_free(void *ptr) Frees the area pointed to by ptr. It is the equivalent of free() but for objects allocated using one of the functions above.