/**
 * @file mem_pool.h
 * \brief Memory pools library.
 *
 * Memory pools library. Library is designed to implement efficient way to
 * store data in memory avoiding calling of many malloc/free. It has overhead
 * because of fact that objects live in pool for rather long time and are not freed
 * immediately after use, but if we know certainly when these objects can be used, we
 * can use pool for them
 */

#ifndef RSPAMD_MEM_POOL_H
#define RSPAMD_MEM_POOL_H

#include "config.h"


#if defined(HAVE_PTHREAD_PROCESS_SHARED) && !defined(DISABLE_PTHREAD_MUTEX)
#include <pthread.h>
#endif

#ifdef  __cplusplus
extern "C" {
#endif

struct f_str_s;

#ifdef __has_attribute
#  if __has_attribute(alloc_size)
#    define RSPAMD_ATTR_ALLOC_SIZE(pos) __attribute__((alloc_size(pos)))
#  else
#    define RSPAMD_ATTR_ALLOC_SIZE(pos)
#  endif

#  if __has_attribute(assume_aligned)
#    define RSPAMD_ATTR_ALLOC_ALIGN(al) __attribute__((assume_aligned(al)))
#  else
#    define RSPAMD_ATTR_ALLOC_ALIGN(al)
#  endif
#  if __has_attribute(returns_nonnull)
#    define RSPAMD_ATTR_RETURNS_NONNUL __attribute__((returns_nonnull))
#  else
#    define RSPAMD_ATTR_RETURNS_NONNUL
#  endif
#else
#define RSPAMD_ATTR_ALLOC_SIZE(pos)
#define RSPAMD_ATTR_ALLOC_ALIGN(al)
#define RSPAMD_ATTR_RETURNS_NONNUL
#endif

#define MEMPOOL_TAG_LEN 20
#define MEMPOOL_UID_LEN 20
#define MIN_MEM_ALIGNMENT   sizeof (guint64)
#define align_ptr(p, a)                                                   \
    (guint8 *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))

enum rspamd_mempool_chain_type {
	RSPAMD_MEMPOOL_NORMAL = 0,
	RSPAMD_MEMPOOL_TMP,
	RSPAMD_MEMPOOL_SHARED,
	RSPAMD_MEMPOOL_MAX
};

/**
 * Destructor type definition
 */
typedef void (*rspamd_mempool_destruct_t) (void *ptr);

/**
 * Pool mutex structure
 */
#if !defined(HAVE_PTHREAD_PROCESS_SHARED) || defined(DISABLE_PTHREAD_MUTEX)
typedef struct memory_pool_mutex_s {
	gint lock;
	pid_t owner;
	guint spin;
} rspamd_mempool_mutex_t;
/**
 * Rwlock for locking shared memory regions
 */
typedef struct memory_pool_rwlock_s {
	rspamd_mempool_mutex_t *__r_lock;                           /**< read mutex (private)								*/
	rspamd_mempool_mutex_t *__w_lock;                           /**< write mutex (private)								*/
} rspamd_mempool_rwlock_t;
#else
typedef pthread_mutex_t rspamd_mempool_mutex_t;
typedef pthread_rwlock_t rspamd_mempool_rwlock_t;
#endif

/**
 * Pool page structure
 */
struct _pool_chain {
	guint8 *begin;                  /**< begin of pool chain block              */
	guint8 *pos;                    /**< current start of free space in block   */
	gsize slice_size;                      /**< length of block                        */
	rspamd_mempool_mutex_t *lock;
	struct _pool_chain *next;
};

/**
 * Destructors list item structure
 */
struct _pool_destructors {
	rspamd_mempool_destruct_t func;         /**< pointer to destructor					*/
	void *data;                             /**< data to free							*/
	const gchar *function;                  /**< function from which this destructor was added */
	const gchar *loc;                       /**< line number                            */
};

/**
 * Tag to use for logging purposes
 */
struct rspamd_mempool_tag {
	gchar tagname[MEMPOOL_TAG_LEN];         /**< readable name							*/
	gchar uid[MEMPOOL_UID_LEN];             /**< unique id								*/
};

/**
 * Memory pool type
 */
struct rspamd_mempool_entry_point;
struct rspamd_mutex_s;
typedef struct memory_pool_s {
	struct _pool_chain *pools[RSPAMD_MEMPOOL_MAX];
	GArray *destructors;
	GPtrArray *trash_stack;
	GHashTable *variables;                  /**< private memory pool variables			*/
	gsize elt_len;                            /**< size of an element						*/
	struct rspamd_mempool_entry_point *entry;
	struct rspamd_mempool_tag tag;          /**< memory pool tag						*/
} rspamd_mempool_t;

/**
 * Statistics structure
 */
typedef struct memory_pool_stat_s {
	guint pools_allocated;              /**< total number of allocated pools					*/
	guint pools_freed;                  /**< number of freed pools								*/
	guint bytes_allocated;              /**< bytes that are allocated with pool allocator		*/
	guint chunks_allocated;             /**< number of chunks that are allocated				*/
	guint shared_chunks_allocated;      /**< shared chunks allocated							*/
	guint chunks_freed;                 /**< chunks freed										*/
	guint oversized_chunks;             /**< oversized chunks									*/
	guint fragmented_size;                /**< fragmentation size								*/
} rspamd_mempool_stat_t;


/**
 * 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, const gchar *loc);

#define rspamd_mempool_new(size, tag) rspamd_mempool_new_((size), (tag), G_STRLOC)

/**
 * Get memory from pool
 * @param pool memory pool object
 * @param size bytes to allocate
 * @return pointer to allocated object
 */
void *rspamd_mempool_alloc (rspamd_mempool_t *pool, gsize size)
RSPAMD_ATTR_ALLOC_SIZE(2) RSPAMD_ATTR_ALLOC_ALIGN(MIN_MEM_ALIGNMENT) RSPAMD_ATTR_RETURNS_NONNUL;

/**
 * Get memory from temporary pool
 * @param pool memory pool object
 * @param size bytes to allocate
 * @return pointer to allocated object
 */
void *rspamd_mempool_alloc_tmp (rspamd_mempool_t *pool, gsize size) RSPAMD_ATTR_RETURNS_NONNUL;

/**
 * Get memory and set it to zero
 * @param pool memory pool object
 * @param size bytes to allocate
 * @return pointer to allocated object
 */
void *rspamd_mempool_alloc0 (rspamd_mempool_t *pool, gsize size)
RSPAMD_ATTR_ALLOC_SIZE(2) RSPAMD_ATTR_ALLOC_ALIGN(MIN_MEM_ALIGNMENT) RSPAMD_ATTR_RETURNS_NONNUL;

/**
 * Get memory and set it to zero
 * @param pool memory pool object
 * @param size bytes to allocate
 * @return pointer to allocated object
 */
void *rspamd_mempool_alloc0_tmp (rspamd_mempool_t *pool, gsize size) RSPAMD_ATTR_RETURNS_NONNUL;

/**
 * Cleanup temporary data in pool
 */
void rspamd_mempool_cleanup_tmp (rspamd_mempool_t *pool);

/**
 * Make a copy of string in pool
 * @param pool memory pool object
 * @param src source string
 * @return pointer to newly created string that is copy of src
 */
gchar *rspamd_mempool_strdup (rspamd_mempool_t *pool, const gchar *src)
RSPAMD_ATTR_ALLOC_ALIGN(MIN_MEM_ALIGNMENT);

/**
 * Make a copy of fixed string in pool as null terminated string
 * @param pool memory pool object
 * @param src source string
 * @return pointer to newly created string that is copy of src
 */
gchar *rspamd_mempool_fstrdup (rspamd_mempool_t *pool,
							   const struct f_str_s *src) RSPAMD_ATTR_ALLOC_ALIGN(MIN_MEM_ALIGNMENT);

struct f_str_tok;

/**
 * Make a copy of fixed string token in pool as null terminated string
 * @param pool memory pool object
 * @param src source string
 * @return pointer to newly created string that is copy of src
 */
gchar *rspamd_mempool_ftokdup (rspamd_mempool_t *pool,
							   const struct f_str_tok *src) RSPAMD_ATTR_ALLOC_ALIGN(MIN_MEM_ALIGNMENT);

/**
 * Allocate piece of shared memory
 * @param pool memory pool object
 * @param size bytes to allocate
 */
void *rspamd_mempool_alloc_shared (rspamd_mempool_t *pool, gsize size)
RSPAMD_ATTR_ALLOC_SIZE(2) RSPAMD_ATTR_ALLOC_ALIGN(MIN_MEM_ALIGNMENT) RSPAMD_ATTR_RETURNS_NONNUL;

void *rspamd_mempool_alloc0_shared (rspamd_mempool_t *pool, gsize size)
RSPAMD_ATTR_ALLOC_SIZE(2) RSPAMD_ATTR_ALLOC_ALIGN(MIN_MEM_ALIGNMENT) RSPAMD_ATTR_RETURNS_NONNUL;

/**
 * Add destructor callback to pool
 * @param pool memory pool object
 * @param func pointer to function-destructor
 * @param data pointer to data that would be passed to destructor
 */
void rspamd_mempool_add_destructor_full (rspamd_mempool_t *pool,
										 rspamd_mempool_destruct_t func,
										 void *data,
										 const gchar *function,
										 const gchar *line);

/* Macros for common usage */
#define rspamd_mempool_add_destructor(pool, func, data) \
    rspamd_mempool_add_destructor_full (pool, func, data, G_STRFUNC, G_STRLOC)

/**
 * Replace destructor callback to pool for specified pointer
 * @param pool memory pool object
 * @param func pointer to function-destructor
 * @param old_data pointer to old data
 * @param new_data pointer to data that would be passed to destructor
 */
void rspamd_mempool_replace_destructor (rspamd_mempool_t *pool,
										rspamd_mempool_destruct_t func, void *old_data, void *new_data);

/**
 * Calls all destructors associated with the specific memory pool without removing
 * of the pool itself
 * @param pool
 */
void rspamd_mempool_destructors_enforce (rspamd_mempool_t *pool);

/**
 * Delete pool, free all its chunks and call destructors chain
 * @param pool memory pool object
 */
void rspamd_mempool_delete (rspamd_mempool_t *pool);

/**
 * Get new mutex from pool (allocated in shared memory)
 * @param pool memory pool object
 * @return mutex object
 */
rspamd_mempool_mutex_t *rspamd_mempool_get_mutex (rspamd_mempool_t *pool);

/**
 * Lock mutex
 * @param mutex mutex to lock
 */
void rspamd_mempool_lock_mutex (rspamd_mempool_mutex_t *mutex);

/**
 * Unlock mutex
 * @param mutex mutex to unlock
 */
void rspamd_mempool_unlock_mutex (rspamd_mempool_mutex_t *mutex);

/**
 * Create new rwlock and place it in shared memory
 * @param pool memory pool object
 * @return rwlock object
 */
rspamd_mempool_rwlock_t *rspamd_mempool_get_rwlock (rspamd_mempool_t *pool);

/**
 * Acquire read lock
 * @param lock rwlock object
 */
void rspamd_mempool_rlock_rwlock (rspamd_mempool_rwlock_t *lock);

/**
 * Acquire write lock
 * @param lock rwlock object
 */
void rspamd_mempool_wlock_rwlock (rspamd_mempool_rwlock_t *lock);

/**
 * Release read lock
 * @param lock rwlock object
 */
void rspamd_mempool_runlock_rwlock (rspamd_mempool_rwlock_t *lock);

/**
 * Release write lock
 * @param lock rwlock object
 */
void rspamd_mempool_wunlock_rwlock (rspamd_mempool_rwlock_t *lock);

/**
 * Get pool allocator statistics
 * @param st stat pool struct
 */
void rspamd_mempool_stat (rspamd_mempool_stat_t *st);

/**
 * Reset memory pool stat
 */
void rspamd_mempool_stat_reset (void);

/**
 * Get optimal pool size based on page size for this system
 * @return size of memory page in system
 */
#define rspamd_mempool_suggest_size() rspamd_mempool_suggest_size_(G_STRLOC)

gsize rspamd_mempool_suggest_size_ (const char *loc);

/**
 * Set memory pool variable
 * @param pool memory pool object
 * @param name name of variable
 * @param gpointer value value of variable
 * @param destructor pointer to function-destructor
 */
void rspamd_mempool_set_variable (rspamd_mempool_t *pool, const gchar *name,
								  gpointer value, rspamd_mempool_destruct_t destructor);

/**
 * Get memory pool variable
 * @param pool memory pool object
 * @param name name of variable
 * @return NULL or pointer to variable data
 */
gpointer rspamd_mempool_get_variable (rspamd_mempool_t *pool,
									  const gchar *name);

/**
 * Removes variable from memory pool
 * @param pool memory pool object
 * @param name name of variable
 */
void rspamd_mempool_remove_variable (rspamd_mempool_t *pool,
									 const gchar *name);

/**
 * Prepend element to a list creating it in the memory pool
 * @param l
 * @param p
 * @return
 */
GList *rspamd_mempool_glist_prepend (rspamd_mempool_t *pool,
									 GList *l, gpointer p) G_GNUC_WARN_UNUSED_RESULT;

/**
 * Append element to a list creating it in the memory pool
 * @param l
 * @param p
 * @return
 */
GList *rspamd_mempool_glist_append (rspamd_mempool_t *pool,
									GList *l, gpointer p) G_GNUC_WARN_UNUSED_RESULT;

#ifdef  __cplusplus
}
#endif

#endif
