diff options
Diffstat (limited to 'src/libutil/mem_pool.c')
-rw-r--r-- | src/libutil/mem_pool.c | 1327 |
1 files changed, 1327 insertions, 0 deletions
diff --git a/src/libutil/mem_pool.c b/src/libutil/mem_pool.c new file mode 100644 index 0000000..119ade3 --- /dev/null +++ b/src/libutil/mem_pool.c @@ -0,0 +1,1327 @@ +/* + * Copyright 2023 Vsevolod Stakhov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "mem_pool.h" +#include "fstring.h" +#include "logger.h" +#include "ottery.h" +#include "unix-std.h" +#include "khash.h" +#include "cryptobox.h" +#include "contrib/uthash/utlist.h" +#include "mem_pool_internal.h" + +#ifdef WITH_JEMALLOC +#include <jemalloc/jemalloc.h> +#if (JEMALLOC_VERSION_MAJOR == 3 && JEMALLOC_VERSION_MINOR >= 6) || (JEMALLOC_VERSION_MAJOR > 3) +#define HAVE_MALLOC_SIZE 1 +#define sys_alloc_size(sz) nallocx(sz, 0) +#endif +#elif defined(__APPLE__) +#include <malloc/malloc.h> +#define HAVE_MALLOC_SIZE 1 +#define sys_alloc_size(sz) malloc_good_size(sz) +#endif + +#ifdef HAVE_SCHED_YIELD +#include <sched.h> +#endif + +/* Sleep time for spin lock in nanoseconds */ +#define MUTEX_SLEEP_TIME 10000000L +#define MUTEX_SPIN_COUNT 100 + +#define POOL_MTX_LOCK() \ + do { \ + } while (0) +#define POOL_MTX_UNLOCK() \ + do { \ + } while (0) + +/* + * This define specify whether we should check all pools for free space for new object + * or just begin scan from current (recently attached) pool + * If MEMORY_GREEDY is defined, then we scan all pools to find free space (more CPU usage, slower + * but requires less memory). If it is not defined check only current pool and if object is too large + * to place in it allocate new one (this may cause huge CPU usage in some cases too, but generally faster than + * greedy method) + */ +#undef MEMORY_GREEDY + + +static inline uint32_t +rspamd_entry_hash(const char *str) +{ + return (guint) rspamd_cryptobox_fast_hash(str, strlen(str), rspamd_hash_seed()); +} + +static inline int +rspamd_entry_equal(const char *k1, const char *k2) +{ + return strcmp(k1, k2) == 0; +} + + +KHASH_INIT(mempool_entry, const gchar *, struct rspamd_mempool_entry_point *, + 1, rspamd_entry_hash, rspamd_entry_equal) + +static khash_t(mempool_entry) *mempool_entries = NULL; + + +/* Internal statistic */ +static rspamd_mempool_stat_t *mem_pool_stat = NULL; +/* Environment variable */ +static gboolean env_checked = FALSE; +static gboolean always_malloc = FALSE; + +/** + * Function that return free space in pool page + * @param x pool page struct + */ +static gsize +pool_chain_free(struct _pool_chain *chain) +{ + gint64 occupied = chain->pos - chain->begin + MIN_MEM_ALIGNMENT; + + return (occupied < (gint64) chain->slice_size ? chain->slice_size - occupied : 0); +} + +/* By default allocate 4Kb chunks of memory */ +#define FIXED_POOL_SIZE 4096 + +static inline struct rspamd_mempool_entry_point * +rspamd_mempool_entry_new(const gchar *loc) +{ + struct rspamd_mempool_entry_point **pentry, *entry; + gint r; + khiter_t k; + + k = kh_put(mempool_entry, mempool_entries, loc, &r); + + if (r >= 0) { + pentry = &kh_value(mempool_entries, k); + entry = g_malloc0(sizeof(*entry)); + *pentry = entry; + memset(entry, 0, sizeof(*entry)); + rspamd_strlcpy(entry->src, loc, sizeof(entry->src)); +#ifdef HAVE_GETPAGESIZE + entry->cur_suggestion = MAX(getpagesize(), FIXED_POOL_SIZE); +#else + entry->cur_suggestion = MAX(sysconf(_SC_PAGESIZE), FIXED_POOL_SIZE); +#endif + } + else { + g_assert_not_reached(); + } + + return entry; +} + +RSPAMD_CONSTRUCTOR(rspamd_mempool_entries_ctor) +{ + if (mempool_entries == NULL) { + mempool_entries = kh_init(mempool_entry); + } +} + +RSPAMD_DESTRUCTOR(rspamd_mempool_entries_dtor) +{ + struct rspamd_mempool_entry_point *elt; + + kh_foreach_value(mempool_entries, elt, { + g_free(elt); + }); + + kh_destroy(mempool_entry, mempool_entries); + mempool_entries = NULL; +} + +static inline struct rspamd_mempool_entry_point * +rspamd_mempool_get_entry(const gchar *loc) +{ + khiter_t k; + struct rspamd_mempool_entry_point *elt; + + if (G_UNLIKELY(!mempool_entries)) { + rspamd_mempool_entries_ctor(); + } + + k = kh_get(mempool_entry, mempool_entries, loc); + + if (k != kh_end(mempool_entries)) { + elt = kh_value(mempool_entries, k); + + return elt; + } + + return rspamd_mempool_entry_new(loc); +} + +static struct _pool_chain * +rspamd_mempool_chain_new(gsize size, gsize alignment, enum rspamd_mempool_chain_type pool_type) +{ + struct _pool_chain *chain; + gsize total_size = size + sizeof(struct _pool_chain) + alignment, + optimal_size = 0; + gpointer map; + + g_assert(size > 0); + + if (pool_type == RSPAMD_MEMPOOL_SHARED) { +#if defined(HAVE_MMAP_ANON) + map = mmap(NULL, + total_size, + PROT_READ | PROT_WRITE, + MAP_ANON | MAP_SHARED, + -1, + 0); + if (map == MAP_FAILED) { + g_error("%s: failed to allocate %" G_GSIZE_FORMAT " bytes", + G_STRLOC, total_size); + abort(); + } + chain = map; + chain->begin = ((guint8 *) chain) + sizeof(struct _pool_chain); +#elif defined(HAVE_MMAP_ZERO) + gint fd; + + fd = open("/dev/zero", O_RDWR); + if (fd == -1) { + return NULL; + } + map = mmap(NULL, + size + sizeof(struct _pool_chain), + PROT_READ | PROT_WRITE, + MAP_SHARED, + fd, + 0); + if (map == MAP_FAILED) { + msg_err("cannot allocate %z bytes, aborting", size + + sizeof(struct _pool_chain)); + abort(); + } + chain = map; + chain->begin = ((guint8 *) chain) + sizeof(struct _pool_chain); +#else +#error No mmap methods are defined +#endif + g_atomic_int_inc(&mem_pool_stat->shared_chunks_allocated); + g_atomic_int_add(&mem_pool_stat->bytes_allocated, total_size); + } + else { +#ifdef HAVE_MALLOC_SIZE + optimal_size = sys_alloc_size(total_size); +#endif + total_size = MAX(total_size, optimal_size); + gint ret = posix_memalign(&map, alignment, total_size); + + if (ret != 0 || map == NULL) { + g_error("%s: failed to allocate %" G_GSIZE_FORMAT " bytes: %d - %s", + G_STRLOC, total_size, ret, strerror(errno)); + abort(); + } + + chain = map; + chain->begin = ((guint8 *) chain) + sizeof(struct _pool_chain); + g_atomic_int_add(&mem_pool_stat->bytes_allocated, total_size); + g_atomic_int_inc(&mem_pool_stat->chunks_allocated); + } + + chain->pos = align_ptr(chain->begin, alignment); + chain->slice_size = total_size - sizeof(struct _pool_chain); + + return chain; +} + + +/** + * Get the current pool of the specified type, creating the corresponding + * array if it's absent + * @param pool + * @param pool_type + * @return + */ +static struct _pool_chain * +rspamd_mempool_get_chain(rspamd_mempool_t *pool, + enum rspamd_mempool_chain_type pool_type) +{ + g_assert(pool_type >= 0 && pool_type < RSPAMD_MEMPOOL_MAX); + + return pool->priv->pools[pool_type]; +} + +static void +rspamd_mempool_append_chain(rspamd_mempool_t *pool, + struct _pool_chain *chain, + enum rspamd_mempool_chain_type pool_type) +{ + g_assert(pool_type >= 0 && pool_type < RSPAMD_MEMPOOL_MAX); + g_assert(chain != NULL); + + LL_PREPEND(pool->priv->pools[pool_type], chain); +} + +/** + * Allocate new memory poll + * @param size size of pool's page + * @return new memory pool object + */ +rspamd_mempool_t * +rspamd_mempool_new_(gsize size, const gchar *tag, gint flags, const gchar *loc) +{ + rspamd_mempool_t *new_pool; + gpointer map; + + /* Allocate statistic structure if it is not allocated before */ + if (mem_pool_stat == NULL) { +#if defined(HAVE_MMAP_ANON) + map = mmap(NULL, + sizeof(rspamd_mempool_stat_t), + PROT_READ | PROT_WRITE, + MAP_ANON | MAP_SHARED, + -1, + 0); + if (map == MAP_FAILED) { + msg_err("cannot allocate %z bytes, aborting", + sizeof(rspamd_mempool_stat_t)); + abort(); + } + mem_pool_stat = (rspamd_mempool_stat_t *) map; +#elif defined(HAVE_MMAP_ZERO) + gint fd; + + fd = open("/dev/zero", O_RDWR); + g_assert(fd != -1); + map = mmap(NULL, + sizeof(rspamd_mempool_stat_t), + PROT_READ | PROT_WRITE, + MAP_SHARED, + fd, + 0); + if (map == MAP_FAILED) { + msg_err("cannot allocate %z bytes, aborting", + sizeof(rspamd_mempool_stat_t)); + abort(); + } + mem_pool_stat = (rspamd_mempool_stat_t *) map; +#else +#error No mmap methods are defined +#endif + memset(map, 0, sizeof(rspamd_mempool_stat_t)); + } + + if (!env_checked) { + /* Check G_SLICE=always-malloc to allow memory pool debug */ + const char *g_slice; + + g_slice = getenv("VALGRIND"); + if (g_slice != NULL) { + always_malloc = TRUE; + } + env_checked = TRUE; + } + + struct rspamd_mempool_entry_point *entry = rspamd_mempool_get_entry(loc); + gsize total_size; + + if (size == 0 && entry) { + size = entry->cur_suggestion; + } + + total_size = sizeof(rspamd_mempool_t) + + sizeof(struct rspamd_mempool_specific) + + MIN_MEM_ALIGNMENT + + sizeof(struct _pool_chain) + + size; + + if (G_UNLIKELY(flags & RSPAMD_MEMPOOL_DEBUG)) { + total_size += sizeof(GHashTable *); + } + /* + * Memory layout: + * struct rspamd_mempool_t + * <optional debug hash table> + * struct rspamd_mempool_specific + * struct _pool_chain + * alignment (if needed) + * memory chunk + */ + guchar *mem_chunk; + gint ret = posix_memalign((void **) &mem_chunk, MIN_MEM_ALIGNMENT, + total_size); + gsize priv_offset; + + if (ret != 0 || mem_chunk == NULL) { + g_error("%s: failed to allocate %" G_GSIZE_FORMAT " bytes: %d - %s", + G_STRLOC, total_size, ret, strerror(errno)); + abort(); + } + + /* Set memory layout */ + new_pool = (rspamd_mempool_t *) mem_chunk; + if (G_UNLIKELY(flags & RSPAMD_MEMPOOL_DEBUG)) { + /* Allocate debug table */ + GHashTable *debug_tbl; + + debug_tbl = g_hash_table_new(rspamd_str_hash, rspamd_str_equal); + memcpy(mem_chunk + sizeof(rspamd_mempool_t), &debug_tbl, + sizeof(GHashTable *)); + priv_offset = sizeof(rspamd_mempool_t) + sizeof(GHashTable *); + } + else { + priv_offset = sizeof(rspamd_mempool_t); + } + + new_pool->priv = (struct rspamd_mempool_specific *) (mem_chunk + + priv_offset); + /* Zero memory for specific and for the first chain */ + memset(new_pool->priv, 0, sizeof(struct rspamd_mempool_specific) + sizeof(struct _pool_chain)); + + new_pool->priv->entry = entry; + new_pool->priv->elt_len = size; + new_pool->priv->flags = flags; + + if (tag) { + rspamd_strlcpy(new_pool->tag.tagname, tag, sizeof(new_pool->tag.tagname)); + } + else { + new_pool->tag.tagname[0] = '\0'; + } + + /* Generate new uid */ + uint64_t uid = rspamd_random_uint64_fast(); + rspamd_encode_hex_buf((unsigned char *) &uid, sizeof(uid), + new_pool->tag.uid, sizeof(new_pool->tag.uid) - 1); + new_pool->tag.uid[sizeof(new_pool->tag.uid) - 1] = '\0'; + + mem_pool_stat->pools_allocated++; + + /* Now we can attach one chunk to speed up simple allocations */ + struct _pool_chain *nchain; + + nchain = (struct _pool_chain *) (mem_chunk + + priv_offset + + sizeof(struct rspamd_mempool_specific)); + + guchar *unaligned = mem_chunk + + priv_offset + + sizeof(struct rspamd_mempool_specific) + + sizeof(struct _pool_chain); + + nchain->slice_size = size; + nchain->begin = unaligned; + nchain->slice_size = size; + nchain->pos = align_ptr(unaligned, MIN_MEM_ALIGNMENT); + new_pool->priv->pools[RSPAMD_MEMPOOL_NORMAL] = nchain; + new_pool->priv->used_memory = size; + + /* Adjust stats */ + g_atomic_int_add(&mem_pool_stat->bytes_allocated, + (gint) size); + g_atomic_int_add(&mem_pool_stat->chunks_allocated, 1); + + return new_pool; +} + +static void * +memory_pool_alloc_common(rspamd_mempool_t *pool, gsize size, gsize alignment, + enum rspamd_mempool_chain_type pool_type, + const gchar *loc) + RSPAMD_ATTR_ALLOC_SIZE(2) RSPAMD_ATTR_ALLOC_ALIGN(MIN_MEM_ALIGNMENT) RSPAMD_ATTR_RETURNS_NONNUL; + + +void rspamd_mempool_notify_alloc_(rspamd_mempool_t *pool, gsize size, const gchar *loc) +{ + if (pool && G_UNLIKELY(pool->priv->flags & RSPAMD_MEMPOOL_DEBUG)) { + GHashTable *debug_tbl = *(GHashTable **) (((guchar *) pool + sizeof(*pool))); + gpointer ptr; + + ptr = g_hash_table_lookup(debug_tbl, loc); + + if (ptr) { + ptr = GSIZE_TO_POINTER(GPOINTER_TO_SIZE(ptr) + size); + } + else { + ptr = GSIZE_TO_POINTER(size); + } + + g_hash_table_insert(debug_tbl, (gpointer) loc, ptr); + } +} + +static void * +memory_pool_alloc_common(rspamd_mempool_t *pool, gsize size, gsize alignment, + enum rspamd_mempool_chain_type pool_type, const gchar *loc) +{ + guint8 *tmp; + struct _pool_chain *new, *cur; + gsize free = 0; + + if (pool) { + POOL_MTX_LOCK(); + pool->priv->used_memory += size; + + if (G_UNLIKELY(pool->priv->flags & RSPAMD_MEMPOOL_DEBUG)) { + rspamd_mempool_notify_alloc_(pool, size, loc); + } + + if (always_malloc && pool_type != RSPAMD_MEMPOOL_SHARED) { + void *ptr; + + if (alignment <= G_MEM_ALIGN) { + ptr = g_malloc(size); + } + else { + ptr = g_malloc(size + alignment); + ptr = align_ptr(ptr, alignment); + } + POOL_MTX_UNLOCK(); + + if (pool->priv->trash_stack == NULL) { + pool->priv->trash_stack = g_ptr_array_sized_new(128); + } + + g_ptr_array_add(pool->priv->trash_stack, ptr); + + return ptr; + } + + cur = rspamd_mempool_get_chain(pool, pool_type); + + /* Find free space in pool chain */ + if (cur) { + free = pool_chain_free(cur); + } + + if (cur == NULL || free < size + alignment) { + if (free < size) { + pool->priv->wasted_memory += free; + } + + /* Allocate new chain element */ + if (pool->priv->elt_len >= size + alignment) { + pool->priv->entry->elts[pool->priv->entry->cur_elts].fragmentation += size; + new = rspamd_mempool_chain_new(pool->priv->elt_len, alignment, + pool_type); + } + else { + mem_pool_stat->oversized_chunks++; + g_atomic_int_add(&mem_pool_stat->fragmented_size, + free); + pool->priv->entry->elts[pool->priv->entry->cur_elts].fragmentation += free; + new = rspamd_mempool_chain_new(size + pool->priv->elt_len, alignment, + pool_type); + } + + /* Connect to pool subsystem */ + rspamd_mempool_append_chain(pool, new, pool_type); + /* No need to align again, aligned by rspamd_mempool_chain_new */ + tmp = new->pos; + new->pos = tmp + size; + POOL_MTX_UNLOCK(); + + return tmp; + } + + /* No need to allocate page */ + tmp = align_ptr(cur->pos, alignment); + cur->pos = tmp + size; + POOL_MTX_UNLOCK(); + + return tmp; + } + + abort(); +} + + +void * +rspamd_mempool_alloc_(rspamd_mempool_t *pool, gsize size, gsize alignment, const gchar *loc) +{ + return memory_pool_alloc_common(pool, size, alignment, RSPAMD_MEMPOOL_NORMAL, loc); +} + +/* + * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX + * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW + */ +#define MUL_NO_OVERFLOW (1UL << (sizeof(gsize) * 4)) + +void * +rspamd_mempool_alloc_array_(rspamd_mempool_t *pool, gsize nmemb, gsize size, gsize alignment, const gchar *loc) +{ + if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + nmemb > 0 && G_MAXSIZE / nmemb < size) { + + g_error("alloc_array: overflow %" G_GSIZE_FORMAT " * %" G_GSIZE_FORMAT "", + nmemb, size); + g_abort(); + } + return memory_pool_alloc_common(pool, size * nmemb, alignment, RSPAMD_MEMPOOL_NORMAL, loc); +} + +void * +rspamd_mempool_alloc0_(rspamd_mempool_t *pool, gsize size, gsize alignment, const gchar *loc) +{ + void *pointer = rspamd_mempool_alloc_(pool, size, alignment, loc); + memset(pointer, 0, size); + + return pointer; +} +void * +rspamd_mempool_alloc0_shared_(rspamd_mempool_t *pool, gsize size, gsize alignment, const gchar *loc) +{ + void *pointer = rspamd_mempool_alloc_shared_(pool, size, alignment, loc); + + memset(pointer, 0, size); + return pointer; +} + +void * +rspamd_mempool_alloc_shared_(rspamd_mempool_t *pool, gsize size, gsize alignment, const gchar *loc) +{ + return memory_pool_alloc_common(pool, size, alignment, RSPAMD_MEMPOOL_SHARED, loc); +} + + +gchar * +rspamd_mempool_strdup_(rspamd_mempool_t *pool, const gchar *src, const gchar *loc) +{ + if (src == NULL) { + return NULL; + } + return rspamd_mempool_strdup_len_(pool, src, strlen(src), loc); +} + +gchar * +rspamd_mempool_strdup_len_(rspamd_mempool_t *pool, const gchar *src, gsize len, const gchar *loc) +{ + gchar *newstr; + + if (src == NULL) { + return NULL; + } + + newstr = rspamd_mempool_alloc_(pool, len + 1, MIN_MEM_ALIGNMENT, loc); + memcpy(newstr, src, len); + newstr[len] = '\0'; + + return newstr; +} + +gchar * +rspamd_mempool_ftokdup_(rspamd_mempool_t *pool, const rspamd_ftok_t *src, + const gchar *loc) +{ + gchar *newstr; + + if (src == NULL) { + return NULL; + } + + newstr = rspamd_mempool_alloc_(pool, src->len + 1, MIN_MEM_ALIGNMENT, loc); + memcpy(newstr, src->begin, src->len); + newstr[src->len] = '\0'; + + return newstr; +} + +void rspamd_mempool_add_destructor_full(rspamd_mempool_t *pool, + rspamd_mempool_destruct_t func, + void *data, + const gchar *function, + const gchar *line) +{ + struct _pool_destructors *cur; + + POOL_MTX_LOCK(); + cur = rspamd_mempool_alloc_(pool, sizeof(*cur), + RSPAMD_ALIGNOF(struct _pool_destructors), line); + cur->func = func; + cur->data = data; + cur->function = function; + cur->loc = line; + cur->next = NULL; + + if (pool->priv->dtors_tail) { + pool->priv->dtors_tail->next = cur; + pool->priv->dtors_tail = cur; + } + else { + pool->priv->dtors_head = cur; + pool->priv->dtors_tail = cur; + } + + POOL_MTX_UNLOCK(); +} + +void rspamd_mempool_replace_destructor(rspamd_mempool_t *pool, + rspamd_mempool_destruct_t func, + void *old_data, + void *new_data) +{ + struct _pool_destructors *tmp; + + LL_FOREACH(pool->priv->dtors_head, tmp) + { + if (tmp->func == func && tmp->data == old_data) { + tmp->func = func; + tmp->data = new_data; + break; + } + } +} + +static gint +cmp_int(gconstpointer a, gconstpointer b) +{ + gint i1 = *(const gint *) a, i2 = *(const gint *) b; + + return i1 - i2; +} + +static void +rspamd_mempool_adjust_entry(struct rspamd_mempool_entry_point *e) +{ + gint sz[G_N_ELEMENTS(e->elts)], sel_pos, sel_neg; + guint i, jitter; + + for (i = 0; i < G_N_ELEMENTS(sz); i++) { + sz[i] = e->elts[i].fragmentation - (gint) e->elts[i].leftover; + } + + qsort(sz, G_N_ELEMENTS(sz), sizeof(gint), cmp_int); + jitter = rspamd_random_uint64_fast() % 10; + /* + * Take stochastic quantiles + */ + sel_pos = sz[50 + jitter]; + sel_neg = sz[4 + jitter]; + + if (-sel_neg > sel_pos) { + /* We need to reduce current suggestion */ + e->cur_suggestion /= (1 + (((double) -sel_neg) / e->cur_suggestion)) * 1.5; + } + else { + /* We still want to grow */ + e->cur_suggestion *= (1 + (((double) sel_pos) / e->cur_suggestion)) * 1.5; + } + + /* Some sane limits counting mempool architecture */ + if (e->cur_suggestion < 1024) { + e->cur_suggestion = 1024; + } + else if (e->cur_suggestion > 1024 * 1024 * 10) { + e->cur_suggestion = 1024 * 1024 * 10; + } + + memset(e->elts, 0, sizeof(e->elts)); +} + +static void +rspamd_mempool_variables_cleanup(rspamd_mempool_t *pool) +{ + if (pool->priv->variables) { + struct rspamd_mempool_variable *var; + kh_foreach_value_ptr(pool->priv->variables, var, { + if (var->dtor) { + var->dtor(var->data); + } + }); + + if (pool->priv->entry && pool->priv->entry->cur_vars < + kh_size(pool->priv->variables)) { + /* + * Increase preallocated size in two cases: + * 1) Our previous guess was zero + * 2) Our new variables count is not more than twice larger than + * previous count + * 3) Our variables count is less than some hard limit + */ + static const guint max_preallocated_vars = 512; + + guint cur_size = kh_size(pool->priv->variables); + guint old_guess = pool->priv->entry->cur_vars; + guint new_guess; + + if (old_guess == 0) { + new_guess = MIN(cur_size, max_preallocated_vars); + } + else { + if (old_guess * 2 < cur_size) { + new_guess = MIN(cur_size, max_preallocated_vars); + } + else { + /* Too large step */ + new_guess = MIN(old_guess * 2, max_preallocated_vars); + } + } + + pool->priv->entry->cur_vars = new_guess; + } + + kh_destroy(rspamd_mempool_vars_hash, pool->priv->variables); + pool->priv->variables = NULL; + } +} + +void rspamd_mempool_destructors_enforce(rspamd_mempool_t *pool) +{ + struct _pool_destructors *destructor; + + POOL_MTX_LOCK(); + + LL_FOREACH(pool->priv->dtors_head, destructor) + { + /* Avoid calling destructors for NULL pointers */ + if (destructor->data != NULL) { + destructor->func(destructor->data); + } + } + + pool->priv->dtors_head = pool->priv->dtors_tail = NULL; + + rspamd_mempool_variables_cleanup(pool); + + POOL_MTX_UNLOCK(); +} + +struct mempool_debug_elt { + gsize sz; + const gchar *loc; +}; + +static gint +rspamd_mempool_debug_elt_cmp(const void *a, const void *b) +{ + const struct mempool_debug_elt *e1 = a, *e2 = b; + + /* Inverse order */ + return (gint) ((gssize) e2->sz) - ((gssize) e1->sz); +} + +void rspamd_mempool_delete(rspamd_mempool_t *pool) +{ + struct _pool_chain *cur, *tmp; + struct _pool_destructors *destructor; + gpointer ptr; + guint i; + gsize len; + + POOL_MTX_LOCK(); + + cur = pool->priv->pools[RSPAMD_MEMPOOL_NORMAL]; + + if (G_UNLIKELY(pool->priv->flags & RSPAMD_MEMPOOL_DEBUG)) { + GHashTable *debug_tbl = *(GHashTable **) (((guchar *) pool) + sizeof(*pool)); + /* Show debug info */ + gsize ndtor = 0; + LL_COUNT(pool->priv->dtors_head, destructor, ndtor); + msg_info_pool("destructing of the memory pool %p; elt size = %z; " + "used memory = %Hz; wasted memory = %Hd; " + "vars = %z; destructors = %z", + pool, + pool->priv->elt_len, + pool->priv->used_memory, + pool->priv->wasted_memory, + pool->priv->variables ? (gsize) kh_size(pool->priv->variables) : (gsize) 0, + ndtor); + + GHashTableIter it; + gpointer k, v; + GArray *sorted_debug_size = g_array_sized_new(FALSE, FALSE, + sizeof(struct mempool_debug_elt), + g_hash_table_size(debug_tbl)); + + g_hash_table_iter_init(&it, debug_tbl); + + while (g_hash_table_iter_next(&it, &k, &v)) { + struct mempool_debug_elt e; + e.loc = (const gchar *) k; + e.sz = GPOINTER_TO_SIZE(v); + g_array_append_val(sorted_debug_size, e); + } + + g_array_sort(sorted_debug_size, rspamd_mempool_debug_elt_cmp); + + for (guint _i = 0; _i < sorted_debug_size->len; _i++) { + struct mempool_debug_elt *e; + + e = &g_array_index(sorted_debug_size, struct mempool_debug_elt, _i); + msg_info_pool("allocated %Hz from %s", e->sz, e->loc); + } + + g_array_free(sorted_debug_size, TRUE); + g_hash_table_unref(debug_tbl); + } + + if (cur && mempool_entries) { + pool->priv->entry->elts[pool->priv->entry->cur_elts].leftover = + pool_chain_free(cur); + + pool->priv->entry->cur_elts = (pool->priv->entry->cur_elts + 1) % + G_N_ELEMENTS(pool->priv->entry->elts); + + if (pool->priv->entry->cur_elts == 0) { + rspamd_mempool_adjust_entry(pool->priv->entry); + } + } + + /* Call all pool destructors */ + LL_FOREACH(pool->priv->dtors_head, destructor) + { + /* Avoid calling destructors for NULL pointers */ + if (destructor->data != NULL) { + destructor->func(destructor->data); + } + } + + rspamd_mempool_variables_cleanup(pool); + + if (pool->priv->trash_stack) { + for (i = 0; i < pool->priv->trash_stack->len; i++) { + ptr = g_ptr_array_index(pool->priv->trash_stack, i); + g_free(ptr); + } + + g_ptr_array_free(pool->priv->trash_stack, TRUE); + } + + for (i = 0; i < G_N_ELEMENTS(pool->priv->pools); i++) { + if (pool->priv->pools[i]) { + LL_FOREACH_SAFE(pool->priv->pools[i], cur, tmp) + { + g_atomic_int_add(&mem_pool_stat->bytes_allocated, + -((gint) cur->slice_size)); + g_atomic_int_add(&mem_pool_stat->chunks_allocated, -1); + + len = cur->slice_size + sizeof(struct _pool_chain); + + if (i == RSPAMD_MEMPOOL_SHARED) { + munmap((void *) cur, len); + } + else { + /* The last pool is special, it is a part of the initial chunk */ + if (cur->next != NULL) { + free(cur); /* Not g_free as we use system allocator */ + } + } + } + } + } + + g_atomic_int_inc(&mem_pool_stat->pools_freed); + POOL_MTX_UNLOCK(); + free(pool); /* allocated by posix_memalign */ +} + +void rspamd_mempool_stat(rspamd_mempool_stat_t *st) +{ + if (mem_pool_stat != NULL) { + st->pools_allocated = mem_pool_stat->pools_allocated; + st->pools_freed = mem_pool_stat->pools_freed; + st->shared_chunks_allocated = mem_pool_stat->shared_chunks_allocated; + st->bytes_allocated = mem_pool_stat->bytes_allocated; + st->chunks_allocated = mem_pool_stat->chunks_allocated; + st->chunks_freed = mem_pool_stat->chunks_freed; + st->oversized_chunks = mem_pool_stat->oversized_chunks; + } +} + +void rspamd_mempool_stat_reset(void) +{ + if (mem_pool_stat != NULL) { + memset(mem_pool_stat, 0, sizeof(rspamd_mempool_stat_t)); + } +} + +gsize rspamd_mempool_suggest_size_(const char *loc) +{ + return 0; +} + +#if !defined(HAVE_PTHREAD_PROCESS_SHARED) || defined(DISABLE_PTHREAD_MUTEX) +/* + * Own emulation + */ +static inline gint +__mutex_spin(rspamd_mempool_mutex_t *mutex) +{ + /* check spin count */ + if (g_atomic_int_dec_and_test(&mutex->spin)) { + /* This may be deadlock, so check owner of this lock */ + if (mutex->owner == getpid()) { + /* This mutex was locked by calling process, so it is just double lock and we can easily unlock it */ + g_atomic_int_set(&mutex->spin, MUTEX_SPIN_COUNT); + return 0; + } + else if (kill(mutex->owner, 0) == -1) { + /* Owner process was not found, so release lock */ + g_atomic_int_set(&mutex->spin, MUTEX_SPIN_COUNT); + return 0; + } + /* Spin again */ + g_atomic_int_set(&mutex->spin, MUTEX_SPIN_COUNT); + } + +#ifdef HAVE_SCHED_YIELD + (void) sched_yield(); +#elif defined(HAVE_NANOSLEEP) + struct timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = MUTEX_SLEEP_TIME; + /* Spin */ + while (nanosleep(&ts, &ts) == -1 && errno == EINTR) + ; +#else +#error No methods to spin are defined +#endif + return 1; +} + +static void +memory_pool_mutex_spin(rspamd_mempool_mutex_t *mutex) +{ + while (!g_atomic_int_compare_and_exchange(&mutex->lock, 0, 1)) { + if (!__mutex_spin(mutex)) { + return; + } + } +} + +rspamd_mempool_mutex_t * +rspamd_mempool_get_mutex(rspamd_mempool_t *pool) +{ + rspamd_mempool_mutex_t *res; + if (pool != NULL) { + res = + rspamd_mempool_alloc_shared(pool, sizeof(rspamd_mempool_mutex_t)); + res->lock = 0; + res->owner = 0; + res->spin = MUTEX_SPIN_COUNT; + return res; + } + return NULL; +} + +void rspamd_mempool_lock_mutex(rspamd_mempool_mutex_t *mutex) +{ + memory_pool_mutex_spin(mutex); + mutex->owner = getpid(); +} + +void rspamd_mempool_unlock_mutex(rspamd_mempool_mutex_t *mutex) +{ + mutex->owner = 0; + (void) g_atomic_int_compare_and_exchange(&mutex->lock, 1, 0); +} + +rspamd_mempool_rwlock_t * +rspamd_mempool_get_rwlock(rspamd_mempool_t *pool) +{ + rspamd_mempool_rwlock_t *lock; + + lock = rspamd_mempool_alloc_shared(pool, sizeof(rspamd_mempool_rwlock_t)); + lock->__r_lock = rspamd_mempool_get_mutex(pool); + lock->__w_lock = rspamd_mempool_get_mutex(pool); + + return lock; +} + +void rspamd_mempool_rlock_rwlock(rspamd_mempool_rwlock_t *lock) +{ + /* Spin on write lock */ + while (g_atomic_int_get(&lock->__w_lock->lock)) { + if (!__mutex_spin(lock->__w_lock)) { + break; + } + } + + g_atomic_int_inc(&lock->__r_lock->lock); + lock->__r_lock->owner = getpid(); +} + +void rspamd_mempool_wlock_rwlock(rspamd_mempool_rwlock_t *lock) +{ + /* Spin on write lock first */ + rspamd_mempool_lock_mutex(lock->__w_lock); + /* Now we have write lock set up */ + /* Wait all readers */ + while (g_atomic_int_get(&lock->__r_lock->lock)) { + __mutex_spin(lock->__r_lock); + } +} + +void rspamd_mempool_runlock_rwlock(rspamd_mempool_rwlock_t *lock) +{ + if (g_atomic_int_get(&lock->__r_lock->lock)) { + (void) g_atomic_int_dec_and_test(&lock->__r_lock->lock); + } +} + +void rspamd_mempool_wunlock_rwlock(rspamd_mempool_rwlock_t *lock) +{ + rspamd_mempool_unlock_mutex(lock->__w_lock); +} +#else + +/* + * Pthread bases shared mutexes + */ +rspamd_mempool_mutex_t * +rspamd_mempool_get_mutex(rspamd_mempool_t *pool) +{ + rspamd_mempool_mutex_t *res; + pthread_mutexattr_t mattr; + + if (pool != NULL) { + res = + rspamd_mempool_alloc_shared(pool, sizeof(rspamd_mempool_mutex_t)); + + pthread_mutexattr_init(&mattr); + pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED); + pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST); + pthread_mutex_init(res, &mattr); + rspamd_mempool_add_destructor(pool, + (rspamd_mempool_destruct_t) pthread_mutex_destroy, res); + pthread_mutexattr_destroy(&mattr); + + return res; + } + return NULL; +} + +void rspamd_mempool_lock_mutex(rspamd_mempool_mutex_t *mutex) +{ + pthread_mutex_lock(mutex); +} + +void rspamd_mempool_unlock_mutex(rspamd_mempool_mutex_t *mutex) +{ + pthread_mutex_unlock(mutex); +} + +rspamd_mempool_rwlock_t * +rspamd_mempool_get_rwlock(rspamd_mempool_t *pool) +{ + rspamd_mempool_rwlock_t *res; + pthread_rwlockattr_t mattr; + + if (pool != NULL) { + res = + rspamd_mempool_alloc_shared(pool, sizeof(rspamd_mempool_rwlock_t)); + + pthread_rwlockattr_init(&mattr); + pthread_rwlockattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED); + pthread_rwlock_init(res, &mattr); + rspamd_mempool_add_destructor(pool, + (rspamd_mempool_destruct_t) pthread_rwlock_destroy, res); + pthread_rwlockattr_destroy(&mattr); + + return res; + } + return NULL; +} + +void rspamd_mempool_rlock_rwlock(rspamd_mempool_rwlock_t *lock) +{ + pthread_rwlock_rdlock(lock); +} + +void rspamd_mempool_wlock_rwlock(rspamd_mempool_rwlock_t *lock) +{ + pthread_rwlock_wrlock(lock); +} + +void rspamd_mempool_runlock_rwlock(rspamd_mempool_rwlock_t *lock) +{ + pthread_rwlock_unlock(lock); +} + +void rspamd_mempool_wunlock_rwlock(rspamd_mempool_rwlock_t *lock) +{ + pthread_rwlock_unlock(lock); +} +#endif + +#define RSPAMD_MEMPOOL_VARS_HASH_SEED 0xb32ad7c55eb2e647ULL +void rspamd_mempool_set_variable(rspamd_mempool_t *pool, + const gchar *name, + gpointer value, + rspamd_mempool_destruct_t destructor) +{ + if (pool->priv->variables == NULL) { + + pool->priv->variables = kh_init(rspamd_mempool_vars_hash); + + if (pool->priv->entry->cur_vars > 0) { + /* Preallocate */ + kh_resize(rspamd_mempool_vars_hash, + pool->priv->variables, + pool->priv->entry->cur_vars); + } + } + + gint hv = rspamd_cryptobox_fast_hash(name, strlen(name), + RSPAMD_MEMPOOL_VARS_HASH_SEED); + khiter_t it; + gint r; + + it = kh_put(rspamd_mempool_vars_hash, pool->priv->variables, hv, &r); + + if (it == kh_end(pool->priv->variables)) { + g_assert_not_reached(); + } + else { + struct rspamd_mempool_variable *pvar; + + if (r == 0) { + /* Existing entry, maybe need cleanup */ + pvar = &kh_val(pool->priv->variables, it); + + if (pvar->dtor) { + pvar->dtor(pvar->data); + } + } + + pvar = &kh_val(pool->priv->variables, it); + pvar->data = value; + pvar->dtor = destructor; + } +} + +gpointer +rspamd_mempool_get_variable(rspamd_mempool_t *pool, const gchar *name) +{ + if (pool->priv->variables == NULL) { + return NULL; + } + + khiter_t it; + gint hv = rspamd_cryptobox_fast_hash(name, strlen(name), + RSPAMD_MEMPOOL_VARS_HASH_SEED); + + it = kh_get(rspamd_mempool_vars_hash, pool->priv->variables, hv); + + if (it != kh_end(pool->priv->variables)) { + struct rspamd_mempool_variable *pvar; + + pvar = &kh_val(pool->priv->variables, it); + return pvar->data; + } + + return NULL; +} + +gpointer +rspamd_mempool_steal_variable(rspamd_mempool_t *pool, const gchar *name) +{ + if (pool->priv->variables == NULL) { + return NULL; + } + + khiter_t it; + gint hv = rspamd_cryptobox_fast_hash(name, strlen(name), + RSPAMD_MEMPOOL_VARS_HASH_SEED); + + it = kh_get(rspamd_mempool_vars_hash, pool->priv->variables, hv); + + if (it != kh_end(pool->priv->variables)) { + struct rspamd_mempool_variable *pvar; + + pvar = &kh_val(pool->priv->variables, it); + kh_del(rspamd_mempool_vars_hash, pool->priv->variables, it); + + return pvar->data; + } + + return NULL; +} + +void rspamd_mempool_remove_variable(rspamd_mempool_t *pool, const gchar *name) +{ + if (pool->priv->variables != NULL) { + khiter_t it; + gint hv = rspamd_cryptobox_fast_hash(name, strlen(name), + RSPAMD_MEMPOOL_VARS_HASH_SEED); + + it = kh_get(rspamd_mempool_vars_hash, pool->priv->variables, hv); + + if (it != kh_end(pool->priv->variables)) { + struct rspamd_mempool_variable *pvar; + + pvar = &kh_val(pool->priv->variables, it); + + if (pvar->dtor) { + pvar->dtor(pvar->data); + } + + kh_del(rspamd_mempool_vars_hash, pool->priv->variables, it); + } + } +} + +GList * +rspamd_mempool_glist_prepend(rspamd_mempool_t *pool, GList *l, gpointer p) +{ + GList *cell; + + cell = rspamd_mempool_alloc(pool, sizeof(*cell)); + cell->prev = NULL; + cell->data = p; + + if (l == NULL) { + cell->next = NULL; + } + else { + cell->next = l; + l->prev = cell; + } + + return cell; +} + +GList * +rspamd_mempool_glist_append(rspamd_mempool_t *pool, GList *l, gpointer p) +{ + GList *cell, *cur; + + cell = rspamd_mempool_alloc(pool, sizeof(*cell)); + cell->next = NULL; + cell->data = p; + + if (l) { + for (cur = l; cur->next != NULL; cur = cur->next) {} + cur->next = cell; + cell->prev = cur; + } + else { + l = cell; + l->prev = NULL; + } + + return l; +} + +gsize rspamd_mempool_get_used_size(rspamd_mempool_t *pool) +{ + return pool->priv->used_memory; +} + +gsize rspamd_mempool_get_wasted_size(rspamd_mempool_t *pool) +{ + return pool->priv->wasted_memory; +} |