diff --git a/src/Makefile_Eina.am b/src/Makefile_Eina.am index 22ade8feba..bb1037e020 100644 --- a/src/Makefile_Eina.am +++ b/src/Makefile_Eina.am @@ -102,7 +102,9 @@ lib/eina/eina_safepointer.h \ lib/eina/eina_inline_safepointer.x \ lib/eina/eina_slice.h \ lib/eina/eina_inline_slice.x \ -lib/eina/eina_inline_modinfo.x +lib/eina/eina_inline_modinfo.x \ +lib/eina/eina_freeq.h + lib_eina_libeina_la_SOURCES = \ lib/eina/eina_abi.c \ @@ -175,7 +177,9 @@ lib/eina/eina_strbuf_common.h \ lib/eina/eina_quaternion.c \ lib/eina/eina_promise.c \ lib/eina/eina_bezier.c \ -lib/eina/eina_safepointer.c +lib/eina/eina_safepointer.c \ +lib/eina/eina_freeq.c + if HAVE_WIN32 lib_eina_libeina_la_SOURCES += lib/eina/eina_file_win32.c @@ -346,7 +350,8 @@ tests/eina/eina_test_vector.c \ tests/eina/eina_test_promise.c \ tests/eina/eina_test_bezier.c \ tests/eina/eina_test_safepointer.c \ -tests/eina/eina_test_slice.c +tests/eina/eina_test_slice.c \ +tests/eina/eina_test_freeq.c tests_eina_eina_suite_CPPFLAGS = -I$(top_builddir)/src/lib/efl \ -DTESTS_WD=\"`pwd`\" \ diff --git a/src/lib/eina/Eina.h b/src/lib/eina/Eina.h index a7a364fe5b..4f1db07e65 100644 --- a/src/lib/eina/Eina.h +++ b/src/lib/eina/Eina.h @@ -272,6 +272,7 @@ extern "C" { #include #include #include +#include #undef EAPI #define EAPI diff --git a/src/lib/eina/eina_freeq.c b/src/lib/eina/eina_freeq.c new file mode 100644 index 0000000000..c259bd883e --- /dev/null +++ b/src/lib/eina/eina_freeq.c @@ -0,0 +1,311 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#include "Eina.h" +#include "eina_private.h" + +#ifdef HAVE_VALGRIND +# include +# include +#endif + +// ========================================================================= // + +#define ITEM_FILLPAT_MAX 0 +#define ITEM_TOTAL_MAX ( 16 * 1024) +#define ITEM_MEM_MAX (1024 * 1024) +#define ITEM_BLOCK_COUNT 42 + +// ========================================================================= // + +typedef struct _Eina_FreeQ_Item Eina_FreeQ_Item; +typedef struct _Eina_FreeQ_Block Eina_FreeQ_Block; + +// ========================================================================= // + +struct _Eina_FreeQ_Item +{ + void *ptr; + void (*free_func) (void *ptr); + size_t size; +}; + +struct _Eina_FreeQ_Block +{ + int start; + int end; + Eina_FreeQ_Block *next; + Eina_FreeQ_Item items[ITEM_BLOCK_COUNT]; +}; + +struct _Eina_FreeQ +{ + Eina_Lock lock; + int count; // number of item slots used + int count_max; // the maximum number of slots allowed to be used + size_t mem_max; // the maximum amount of memory allowed to be used + size_t mem_total; // current total memory known about in the queue + Eina_FreeQ_Block *blocks; // the list of blocks of free items + Eina_FreeQ_Block *block_last; // the last block to append items to +}; + +// ========================================================================= // + +static Eina_FreeQ *_eina_freeq_main = NULL; +static int _eina_freeq_bypass = -1; +static unsigned int _eina_freeq_fillpat_max = ITEM_FILLPAT_MAX; +static unsigned char _eina_freeq_fillpat_val = 0x55; +static int _eina_freeq_total_max = ITEM_TOTAL_MAX; +static size_t _eina_freeq_mem_max = ITEM_MEM_MAX; + +// ========================================================================= // + +static void +_eina_freeq_fill_do(void *ptr, size_t size) +{ + if (ptr) memset(ptr, _eina_freeq_fillpat_val, size); +} + +static void +_eina_freeq_fill_check(void *ptr, void (*free_func) (void *ptr), size_t size) +{ + unsigned char *p0 = ptr, *p = p0, *pe = p + size; + for (; p < pe; p++) + { + if (*p != _eina_freeq_fillpat_val) goto err; + } + return; +err: + EINA_LOG_ERR("Pointer %p size %lu freed by %p has fill error %x != %x @ %lu", + p0, (unsigned long)size, free_func, + (unsigned int)*p, (unsigned int)_eina_freeq_fillpat_val, + (unsigned long)(p - p0)); +} + +static void +_eina_freeq_free_do(void *ptr, + void (*free_func) (void *ptr), + size_t size EINA_UNUSED) +{ + if ((size < _eina_freeq_fillpat_max) && (size > 0)) + _eina_freeq_fill_check(ptr, free_func, size); + free_func(ptr); + return; +} + +static Eina_Bool +_eina_freeq_block_append(Eina_FreeQ *fq) +{ + Eina_FreeQ_Block *fb = malloc(sizeof(Eina_FreeQ_Block)); + if (!fb) return EINA_FALSE; + fb->start = 0; + fb->end = 0; + fb->next = NULL; + if (!fq->blocks) fq->blocks = fb; + else fq->block_last->next = fb; + fq->block_last = fb; + return EINA_TRUE; +} + +static void +_eina_freeq_process(Eina_FreeQ *fq) +{ + Eina_FreeQ_Block *fb = fq->blocks; + if (!fb) return; + _eina_freeq_free_do(fb->items[fb->start].ptr, + fb->items[fb->start].free_func, + fb->items[fb->start].size); + fq->mem_total -= fb->items[fb->start].size; + fb->start++; + fq->count--; + if (fb->start == fb->end) + { + fq->blocks = fb->next; + if (!fq->blocks) fq->block_last = NULL; + free(fb); + } +} + +static void +_eina_freeq_flush_nolock(Eina_FreeQ *fq) +{ + while ((fq->count > fq->count_max) || (fq->mem_total > fq->mem_max)) + _eina_freeq_process(fq); +} + +// ========================================================================= // + +EAPI Eina_FreeQ * +eina_freeq_new(void) +{ + Eina_FreeQ *fq; + + if (_eina_freeq_bypass == -1) + { + const char *s; + + if (getenv("EINA_FREEQ_BYPASS")) _eina_freeq_bypass = 1; +#ifdef HAVE_VALGRIND + else if (RUNNING_ON_VALGRIND) _eina_freeq_bypass = 1; +#endif + else _eina_freeq_bypass = 0; + s = getenv("EINA_FREEQ_FILL_MAX"); + if (s) _eina_freeq_fillpat_max = atoi(s); + s = getenv("EINA_FREEQ_TOTAL_MAX"); + if (s) _eina_freeq_total_max = atoi(s); + s = getenv("EINA_FREEQ_MEM_MAX"); + if (s) _eina_freeq_mem_max = atoi(s) * 1024; + } + fq = calloc(1, sizeof(Eina_FreeQ)); + if (!fq) return NULL; + eina_lock_recursive_new(&(fq->lock)); + fq->count_max = _eina_freeq_total_max; + fq->mem_max = _eina_freeq_mem_max; + return fq; +} + +EAPI void +eina_freeq_free(Eina_FreeQ *fq) +{ + if (!fq) return; + if (fq == _eina_freeq_main) _eina_freeq_main = NULL; + eina_freeq_clear(fq); + eina_lock_free(&(fq->lock)); + free(fq); +} + +EAPI void +eina_freeq_main_set(Eina_FreeQ *fq) +{ + if (!fq) return; + _eina_freeq_main = fq; +} + +EAPI Eina_FreeQ * +eina_freeq_main_get(void) +{ + return _eina_freeq_main; +} + +EAPI void +eina_freeq_count_max_set(Eina_FreeQ *fq, int count) +{ + if (!fq) return; + if (count < 0) count = 0; + eina_lock_take(&(fq->lock)); + fq->count_max = count; + _eina_freeq_flush_nolock(fq); + eina_lock_release(&(fq->lock)); +} + +EAPI int +eina_freeq_count_max_get(Eina_FreeQ *fq) +{ + int count; + + if (!fq) return 0; + eina_lock_take(&(fq->lock)); + count = fq->count_max; + eina_lock_release(&(fq->lock)); + return count; +} + +EAPI void +eina_freeq_mem_max_set(Eina_FreeQ *fq, size_t mem) +{ + if (!fq) return; + eina_lock_take(&(fq->lock)); + fq->mem_max = mem; + _eina_freeq_flush_nolock(fq); + eina_lock_release(&(fq->lock)); +} + +EAPI size_t +eina_freeq_mem_max_get(Eina_FreeQ *fq) +{ + size_t mem; + + if (!fq) return 0; + eina_lock_take(&(fq->lock)); + mem = fq->mem_max; + eina_lock_release(&(fq->lock)); + return mem; +} + +EAPI void +eina_freeq_clear(Eina_FreeQ *fq) +{ + if (!fq) return; + eina_lock_take(&(fq->lock)); + while (fq->count > 0) _eina_freeq_process(fq); + eina_lock_release(&(fq->lock)); +} + +EAPI void +eina_freeq_reduce(Eina_FreeQ *fq, int count) +{ + if (!fq) return; + eina_lock_take(&(fq->lock)); + while ((fq->count > 0) && (count > 0)) + { + _eina_freeq_process(fq); + count--; + } + eina_lock_release(&(fq->lock)); +} + +EAPI Eina_Bool +eina_freeq_ptr_pending(Eina_FreeQ *fq) +{ + Eina_Bool pending; + + if (!fq) return EINA_FALSE; + eina_lock_take(&(fq->lock)); + if (fq->blocks) pending = EINA_TRUE; + else pending = EINA_FALSE; + eina_lock_release(&(fq->lock)); + return pending; +} + +EAPI void +eina_freeq_ptr_add(Eina_FreeQ *fq, + void *ptr, + void (*free_func) (void *ptr), + size_t size) +{ + Eina_FreeQ_Block *fb; + + if (!ptr) return; + if (!free_func) free_func = free; + if ((size < _eina_freeq_fillpat_max) && (size > 0)) + _eina_freeq_fill_do(ptr, size); + if ((!fq) || (_eina_freeq_bypass)) goto insta_free; + eina_lock_take(&(fq->lock)); + if ((!fq->block_last) || (fq->block_last->end == ITEM_BLOCK_COUNT)) + goto newblock; +newblock_done: + fb = fq->block_last; + fb->items[fb->end].ptr = ptr; + fb->items[fb->end].free_func = free_func; + fb->items[fb->end].size = size; + fb->end++; + fq->count++; + fq->mem_total += size; + _eina_freeq_flush_nolock(fq); + eina_lock_release(&(fq->lock)); + return; +newblock: + if (!_eina_freeq_block_append(fq)) + { + eina_lock_release(&(fq->lock)); +insta_free: + _eina_freeq_free_do(ptr, free_func, size); + return; + } + goto newblock_done; +} diff --git a/src/lib/eina/eina_freeq.h b/src/lib/eina/eina_freeq.h new file mode 100644 index 0000000000..db3d0476f5 --- /dev/null +++ b/src/lib/eina/eina_freeq.h @@ -0,0 +1,276 @@ +#ifndef EINA_FREEQ_H_ +#define EINA_FREEQ_H_ + +#include + +#include "eina_config.h" + +#include "eina_types.h" + +/** + * @addtogroup Eina_FreeQ_Group Free Queue Group + * @ingroup Eina + * + * @brief This provides a mechanism to defer actual freeing of memory + * data at some time in the future. The main free queue will be driven + * by the EFL main loop and ensure data is eventually freed. + * + * For debugging and tuning you may set the following envrionment variables: + * + * EINA_FREEQ_BYPASS=1 + * + * Set this environment variable to immediately bypass the free queue and + * have all items submitted free with their free function immediately. + * + * EINA_FREEQ_FILL_MAX=N + * + * This sets the maximum number of bytes to N an item in the free queue may + * be in size for the free queue to fill it with debugging values like + * 0x55 in every byte, to ensure you can see what memory has been freed + * or not when debugging in tools like gdb. + * + * EINA_FREEQ_TOTAL_MAX=N + * + * This sets the maximum number of items allowed to N on a free queue by + * default before it starts emptying the free queue out tomake room. + * + * EINA_FREEQ_MEM_MAX=N + * + * This sets the maximum total number of Kb (Kilobytes) of memory allowed + * on a free queue by default to N Kb worth of data. + * + * @{ + * + * @since 1.19 + * + * @typedef Eina_FreeQ + * + * A queue of pointers to free in the future. You may create custom free + * queues of your own to defer freeing, use the main free queue where the + * mainloop will free thnigs as it iterates, or eina will free everything + * on shut down. + * + */ +typedef struct _Eina_FreeQ Eina_FreeQ; + +/** + * @brief Create a new free queue to defer freeing of data with + * + * @return A new free queue + * @since 1.19 + */ +EAPI Eina_FreeQ * +eina_freeq_new(void); + +/** + * @brief Free a free queue and anything that is queued in it. + * + * @param fq The free queue to free and clear. + * + * @since 1.19 + */ +EAPI void +eina_freeq_free(Eina_FreeQ *fq); + +/** + * @brief Set the main free queue driven by the EFL mainloop. + * + * @param fq The free queue to set as the main loop one. + * + * @since 1.19 + */ +EAPI void +eina_freeq_main_set(Eina_FreeQ *fq); + +/** + * @brief Get the main loop free queue. + * + * @return The main loop free queue. + * + * @since 1.19 + */ +EAPI Eina_FreeQ * +eina_freeq_main_get(void); + +/** + * @brief Set the maximum number of free pointers this queue is allowed + * + * @param fq The free queue to alter + * @param count The maximum number of items allowed (must be >= 0) + * + * This will alter the maximum number of pointers allowed in the given free + * queue. If more items are added to the free queue than are allowed, + * excess items will be freed to make room for the new items. If count is + * changed, then excess items may be cleaned out at the time this API is + * called. + * + * @since 1.19 + */ +EAPI void +eina_freeq_count_max_set(Eina_FreeQ *fq, int count); + +/** + * @brief Get the maximum number of free pointers this queue is allowed + * + * @param fq The free queue to query + * @return The maximum number of free items allowed + * + * @since 1.19 + */ +EAPI int +eina_freeq_count_max_get(Eina_FreeQ *fq); + +/** + * @brief Set the maximum amount of memory allowed + * + * @param fq The free queue to alter + * @param mem The maximum memory in bytes + * + * This will alter the maximum amount of memory allowed for pointers stored + * in the free queue. The size used is the size give, so items given that + * are 0 sized will not contribute to this limit. If items with a total + * memory footprint are added to the free queue, items will be cleaned out + * until the total is below this limit. Changing the limit may involve + * cleaning out excess items from the free queue until the total amount of + * memory used by items in the queue is below or at the limit. + * + * @since 1.19 + */ +EAPI void +eina_freeq_mem_max_set(Eina_FreeQ *fq, size_t mem); + +/** + * @brief Get the maximum amount of memory allowed + * + * @param fq The free queue to query + * @return The maximum amount of memory in bytes + * + * @since 1.19 + */ +EAPI size_t +eina_freeq_mem_max_get(Eina_FreeQ *fq); + +/** + * @brief Clear out all queued items to be freed by freeing them + * + * @param fq The free queue to clear + * + * This will free and thus remove all queued items from the free queue when + * this function is called. When it returns the free queue should be + * empty. + * + * @since 1.19 + */ +EAPI void +eina_freeq_clear(Eina_FreeQ *fq); + +/** + * @brief Reduce the number of items in the free queue by up to @p count + * + * @param fq The free queue to reduce in item count + * @param count The number of items to try and free + * + * This will attempt to free up to @p count items from the given free queue + * and thus reduce the amount of memory it is holding on to. This function + * will return once it has removed @p count items or there are no more items + * to remove from the queue. + * + * @since 1.19 + */ +EAPI void +eina_freeq_reduce(Eina_FreeQ *fq, int count); + +/** + * @brief Return if there are any items pending a free in the free queue + * + * @param fq The free queue to query + * @raturn EINA_TRUE if there are items to free, EINA_FALSE otherwise + * + * @since 1.19 + */ +EAPI Eina_Bool +eina_freeq_ptr_pending(Eina_FreeQ *fq); + +/** + * @brief Add a pointer with free function and size to the free queue + * + * @param fq The free queue to add the pointer to + * @param ptr The pointer to free + * @param free_func The function used to free the pointer with + * @param size The size of the data the pointer points to + * + * This adds the given @p ptr pointer to the queue to be freed later on. + * The function @p free_func will be used, or if this is NULL, it is assumed + * the libc free() function will be used then instead. The @p size parameter + * determines the size of the data pointed to, but if this is 0 then no + * assumptions are made about size and the pointer is considered opaque. A + * zero sized pointer will not contribute to the total memory usage of + * items in the queue as well. If @p size is supplied it must be correct + * as the memory may be written to for debugging purposes or otherwise + * inspected or checksummed. Once a pointer is added to the free queue + * with this API the memory should be considered freed as if the real + * @p free_func was called immediately (and it may actually be called + * immediately if certain environment variables are set). A free queue exists + * to move the cost of freeing to another point in time when it is more + * convenient to do so as well as provide some robustness for badly + * written code that may access memory after freeing. Note that when using + * tools like valgrind, eina detects this and will also immediately free + * the data so valgrind's own memory checkers can detect use after free + * as normal. + * + * @since 1.19 + */ +EAPI void +eina_freeq_ptr_add(Eina_FreeQ *fq, void *ptr, void (*free_func) (void *ptr), size_t size); + +/** + * @brief Add a pointer to the main free queue + * + * @param ptr The pointer to free + * @param free_func The function used to free the pointer with + * @param size The size of the data the pointer points to + * + * This is the same as eina_freeq_ptr_add() but the main free queue is + * fetched by eina_freeq_main_get(). + * + * @since 1.19 + */ +static inline void +eina_freeq_ptr_main_add(void *ptr, void (*free_func) (void *ptr), size_t size) +{ + eina_freeq_ptr_add(eina_freeq_main_get(), ptr, free_func, size); +} + +/** + * @brief Convenience macro for well known structures and types + * + * @param ptr The pointer to free + * + * This is the same as eina_freeq_ptr_main_add() but the free function is + * assumed to be the libc free() function, and size is provided by + * sizeof(*ptr), so it will not work on void pointers or will be inaccurate + * for pointers to arrays. For arrays please use EINA_FREEQ_ARRAY_FREE() + * + * @since 1.19 + */ +#define EINA_FREEQ_FREE(ptr) eina_freeq_ptr_main_add(ptr, NULL, sizeof(*(ptr))) + +/** + * @brief Convenience macro for well known structures and types + * + * @param ptr The pointer to free + * + * This is the same as eina_freeq_ptr_main_add() but the free function is + * assumed to be the libc free() function, and size is provided by + * sizeof(*ptr), so it will not work on void pointers. Total size is multiplied + * by the count @p n so it should work well for arrays of types. + * + * @since 1.19 + */ +#define EINA_FREEQ_N_FREE(ptr, n) eina_freeq_ptr_main_add(ptr, NULL, sizeof(*(ptr)) * n) + +/** + * @} + */ + +#endif diff --git a/src/lib/eina/eina_main.c b/src/lib/eina/eina_main.c index 118b340cbe..70bd7586e2 100644 --- a/src/lib/eina/eina_main.c +++ b/src/lib/eina/eina_main.c @@ -68,6 +68,7 @@ #include "eina_inarray.h" #include "eina_value.h" #include "eina_evlog.h" +#include "eina_freeq.h" /* no model for now #include "eina_model.h" */ @@ -265,6 +266,7 @@ eina_init(void) mtrace(); } #endif + eina_freeq_main_set(eina_freeq_new()); if (!eina_log_init()) { @@ -332,6 +334,7 @@ eina_shutdown(void) #ifdef EINA_HAVE_DEBUG_THREADS pthread_mutex_destroy(&_eina_tracking_lock); #endif + eina_freeq_free(eina_freeq_main_get()); #ifdef MT if (_mt_enabled) { diff --git a/src/tests/eina/eina_suite.c b/src/tests/eina/eina_suite.c index 2c35a11536..7bdd62f742 100644 --- a/src/tests/eina/eina_suite.c +++ b/src/tests/eina/eina_suite.c @@ -81,6 +81,7 @@ static const Efl_Test_Case etc[] = { { "Bezier", eina_test_bezier }, { "SafePointer", eina_test_safepointer }, { "Slice", eina_test_slice }, + { "Free Queue", eina_test_freeq }, { NULL, NULL } }; diff --git a/src/tests/eina/eina_suite.h b/src/tests/eina/eina_suite.h index 3ff362e7bd..3bda471d1f 100644 --- a/src/tests/eina/eina_suite.h +++ b/src/tests/eina/eina_suite.h @@ -73,5 +73,6 @@ void eina_test_promise(TCase *tc); void eina_test_bezier(TCase *tc); void eina_test_safepointer(TCase *tc); void eina_test_slice(TCase *tc); +void eina_test_freeq(TCase *tc); #endif /* EINA_SUITE_H_ */ diff --git a/src/tests/eina/eina_test_freeq.c b/src/tests/eina/eina_test_freeq.c new file mode 100644 index 0000000000..bf732b9be0 --- /dev/null +++ b/src/tests/eina/eina_test_freeq.c @@ -0,0 +1,122 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include "eina_suite.h" + +START_TEST(freeq_simple) +{ + Eina_FreeQ *fq, *pfq; + + eina_init(); + + fail_if(eina_freeq_main_get() == NULL); + pfq = eina_freeq_main_get(); + + fq = eina_freeq_new(); + fail_if(!fq); + + eina_freeq_main_set(fq); + fail_if(eina_freeq_main_get() != fq); + + eina_freeq_free(fq); + fail_if(eina_freeq_main_get() != NULL); + eina_freeq_main_set(pfq); + + eina_shutdown(); +} +END_TEST + +static int _n = 0; + +static void freefn(void *data) +{ + free(data); + _n--; +} + +START_TEST(freeq_tune) +{ + void *p; + + eina_init(); + + eina_freeq_count_max_set(eina_freeq_main_get(), 3); + fail_if(eina_freeq_count_max_get(eina_freeq_main_get()) != 3); + + eina_freeq_mem_max_set(eina_freeq_main_get(), 20); + fail_if(eina_freeq_mem_max_get(eina_freeq_main_get()) != 20); + + _n++; + p = malloc(9); + eina_freeq_ptr_main_add(p, freefn, 9); + _n++; + p = malloc(9); + eina_freeq_ptr_main_add(p, freefn, 9); + _n++; + p = malloc(9); + eina_freeq_ptr_main_add(p, freefn, 9); + eina_freeq_ptr_main_add(NULL, freefn, 9); + fail_if(_n > 2); + + eina_freeq_count_max_set(eina_freeq_main_get(), 1); + fail_if(_n > 1); + + eina_freeq_clear(eina_freeq_main_get()); + fail_if(_n > 0); + + fail_if(eina_freeq_ptr_pending(eina_freeq_main_get()) == EINA_TRUE); + + eina_shutdown(); +} +END_TEST + +START_TEST(freeq_reduce) +{ + void *p; + + eina_init(); + + _n++; + p = malloc(9); + eina_freeq_ptr_main_add(p, freefn, 9); + _n++; + p = malloc(9); + eina_freeq_ptr_main_add(p, freefn, 9); + _n++; + p = malloc(9); + eina_freeq_ptr_main_add(p, freefn, 9); + + while (eina_freeq_ptr_pending(eina_freeq_main_get())) + eina_freeq_reduce(eina_freeq_main_get(), 1); + fail_if(_n > 0); + fail_if(eina_freeq_ptr_pending(eina_freeq_main_get()) == EINA_TRUE); + + _n++; + p = malloc(9); + eina_freeq_ptr_main_add(p, freefn, 9); + _n++; + p = malloc(9); + eina_freeq_ptr_main_add(p, freefn, 9); + _n++; + p = malloc(9); + eina_freeq_ptr_main_add(p, freefn, 9); + + while (eina_freeq_ptr_pending(eina_freeq_main_get())) + eina_freeq_reduce(eina_freeq_main_get(), 5); + fail_if(_n > 0); + fail_if(eina_freeq_ptr_pending(eina_freeq_main_get()) == EINA_TRUE); + + eina_shutdown(); +} +END_TEST + +void +eina_test_freeq(TCase *tc) +{ + tcase_add_test(tc, freeq_simple); + tcase_add_test(tc, freeq_tune); + tcase_add_test(tc, freeq_reduce); +}