diff --git a/src/lib/eina/eina_freeq.c b/src/lib/eina/eina_freeq.c index 17459dc5ee..0485501c03 100644 --- a/src/lib/eina/eina_freeq.c +++ b/src/lib/eina/eina_freeq.c @@ -45,14 +45,16 @@ struct _Eina_FreeQ_Block struct _Eina_FreeQ { - Eina_Lock lock; + Eina_Lock lock; // recursive lock, unused for postponed queues (thread-local) int count; // number of item slots used - int count_max; // the maximum number of slots allowed to be used + int count_max; // maximum number of slots allowed to be used or -1 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 - int bypass; // 0 if not to bypass, 1 if we should bypass + Eina_Bool bypass; // 0 if not to bypass, 1 if we should bypass + Eina_Bool postponed; // 1 if postponed type of freeq (eg. temp strings) + Eina_Bool unlocked; // 0 by default, 1 if thread-local (lock not used) }; // ========================================================================= // @@ -67,6 +69,13 @@ static size_t _eina_freeq_mem_max = ITEM_MEM_MAX; // ========================================================================= // +#define LOCK_FQ(fq); do { \ + if (!fq->unlocked) eina_lock_take(&(fq->lock)); } while(0) +#define UNLOCK_FQ(fq); do { \ + if (!fq->unlocked) eina_lock_release(&(fq->lock)); } while(0) + +// ========================================================================= // + static inline void _eina_freeq_fill_do(void *ptr, size_t size) { @@ -146,18 +155,20 @@ _eina_freeq_process(Eina_FreeQ *fq) static void _eina_freeq_flush_nolock(Eina_FreeQ *fq) { + if (fq->postponed) return; + while ((fq->count > fq->count_max) || (fq->mem_total > fq->mem_max)) _eina_freeq_process(fq); } // ========================================================================= // -EAPI Eina_FreeQ * -eina_freeq_new(void) +static Eina_FreeQ * +_eina_freeq_new_default(void) { Eina_FreeQ *fq; - if (_eina_freeq_bypass == -1) + if (EINA_UNLIKELY(_eina_freeq_bypass == -1)) { const char *s; int v; @@ -197,16 +208,53 @@ eina_freeq_new(void) return fq; } +static Eina_FreeQ * +_eina_freeq_new_postponed(void) +{ + Eina_FreeQ *fq; + + fq= calloc(1, sizeof(*fq)); + if (!fq) return NULL; + fq->mem_max = 0; + fq->count_max = -1; + fq->postponed = EINA_TRUE; + fq->unlocked = EINA_TRUE; + return fq; +} + +EAPI Eina_FreeQ * +eina_freeq_new(Eina_FreeQ_Type type) +{ + switch (type) + { + case EINA_FREEQ_DEFAULT: + return _eina_freeq_new_default(); + case EINA_FREEQ_POSTPONED: + return _eina_freeq_new_postponed(); + default: + return NULL; + } +} + 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)); + if (!fq->unlocked) eina_lock_free(&(fq->lock)); free(fq); } +EAPI Eina_FreeQ_Type +eina_freeq_type_get(Eina_FreeQ *fq) +{ + if (fq && fq->postponed) + return EINA_FREEQ_POSTPONED; + return EINA_FREEQ_DEFAULT; +} + +/* FIXME This function should not exist as an EAPI */ EAPI void eina_freeq_main_set(Eina_FreeQ *fq) { @@ -224,12 +272,13 @@ 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)); + if (fq->postponed) return; + if (count < 0) count = -1; + LOCK_FQ(fq); fq->bypass = 0; fq->count_max = count; _eina_freeq_flush_nolock(fq); - eina_lock_release(&(fq->lock)); + UNLOCK_FQ(fq); } EAPI int @@ -238,10 +287,10 @@ eina_freeq_count_max_get(Eina_FreeQ *fq) int count; if (!fq) return 0; - eina_lock_take(&(fq->lock)); + LOCK_FQ(fq); if (fq->bypass) count = 0; else count = fq->count_max; - eina_lock_release(&(fq->lock)); + UNLOCK_FQ(fq); return count; } @@ -249,11 +298,12 @@ EAPI void eina_freeq_mem_max_set(Eina_FreeQ *fq, size_t mem) { if (!fq) return; - eina_lock_take(&(fq->lock)); + if (fq->postponed) return; + LOCK_FQ(fq); fq->bypass = 0; fq->mem_max = mem; _eina_freeq_flush_nolock(fq); - eina_lock_release(&(fq->lock)); + UNLOCK_FQ(fq); } EAPI size_t @@ -262,10 +312,10 @@ eina_freeq_mem_max_get(Eina_FreeQ *fq) size_t mem; if (!fq) return 0; - eina_lock_take(&(fq->lock)); + LOCK_FQ(fq); if (fq->bypass) mem = 0; else mem = fq->mem_max; - eina_lock_release(&(fq->lock)); + UNLOCK_FQ(fq); return mem; } @@ -273,22 +323,22 @@ EAPI void eina_freeq_clear(Eina_FreeQ *fq) { if (!fq) return; - eina_lock_take(&(fq->lock)); + LOCK_FQ(fq); while (fq->count > 0) _eina_freeq_process(fq); - eina_lock_release(&(fq->lock)); + UNLOCK_FQ(fq); } EAPI void eina_freeq_reduce(Eina_FreeQ *fq, int count) { if (!fq) return; - eina_lock_take(&(fq->lock)); + LOCK_FQ(fq); while ((fq->count > 0) && (count > 0)) { _eina_freeq_process(fq); count--; } - eina_lock_release(&(fq->lock)); + UNLOCK_FQ(fq); } EAPI Eina_Bool @@ -297,10 +347,10 @@ eina_freeq_ptr_pending(Eina_FreeQ *fq) Eina_Bool pending; if (!fq) return EINA_FALSE; - eina_lock_take(&(fq->lock)); + LOCK_FQ(fq); if (fq->blocks) pending = EINA_TRUE; else pending = EINA_FALSE; - eina_lock_release(&(fq->lock)); + UNLOCK_FQ(fq); return pending; } @@ -314,13 +364,30 @@ eina_freeq_ptr_add(Eina_FreeQ *fq, if (!ptr) return; if (!free_func) free_func = free; - if ((size < _eina_freeq_fillpat_max) && (size > 0)) + if (!fq->postponed && (size < _eina_freeq_fillpat_max) && (size > 0)) _eina_freeq_fill_do(ptr, size); - if ((!fq) || (fq->bypass)) goto insta_free; - eina_lock_take(&(fq->lock)); + + if (!fq || fq->bypass) + { + _eina_freeq_free_do(ptr, free_func, size); + return; + } + + LOCK_FQ(fq); if ((!fq->block_last) || (fq->block_last->end == ITEM_BLOCK_COUNT)) - goto newblock; -newblock_done: + { + if (!_eina_freeq_block_append(fq)) + { + UNLOCK_FQ(fq); + if (!fq->postponed) + _eina_freeq_free_do(ptr, free_func, size); + else + EINA_LOG_ERR("Could not add a pointer to the free queue! This " + "program will leak resources!"); + return; + } + } + fb = fq->block_last; fb->items[fb->end].ptr = ptr; fb->items[fb->end].free_func = free_func; @@ -329,15 +396,5 @@ newblock_done: 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; + UNLOCK_FQ(fq); } diff --git a/src/lib/eina/eina_freeq.h b/src/lib/eina/eina_freeq.h index eacd3a0ca9..643aa1e5ce 100644 --- a/src/lib/eina/eina_freeq.h +++ b/src/lib/eina/eina_freeq.h @@ -15,7 +15,8 @@ * 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: + * For debugging and tuning you may set the following environment variables, + * applicable only to free queues of the default type: * * EINA_FREEQ_BYPASS=1/0 * @@ -76,6 +77,48 @@ */ typedef struct _Eina_FreeQ Eina_FreeQ; +/** @brief Type of free queues + * + * @since 1.19 + */ +typedef enum _Eina_FreeQ_Type +{ + /** @brief Default type of free queue. + * + * Default free queue, any object added to it should be considered freed + * immediately. Use this kind of freeq for debugging and additional memory + * safety purposes only. + * + * This type of free queue is thread-safe. + * + * @since 1.19 + */ + EINA_FREEQ_DEFAULT, + + /** @brief Postponed type of free queue. + * + * Postponed free queues behave differently in that objects added to it + * are not to be considered freed immediately, but rather they are + * short-lived. Use this to return temporary objects that may be used only + * in the local scope. The queued objects lifetime ends as soon as the + * execution comes back to the loop. Objects added to this kind of free + * queue should be accessed exclusively from the same thread that adds them. + * + * If a thread does not have a loop attached, the application may leak all + * those objects. At the moment of writing this means only the main loop + * should use such a free queue. + * + * By default those queues have no memory limit, and will be entirely + * flushed when the execution comes back to the loop. + * + * This type of free queue is not thread-safe and should be considered local + * to a single thread. + * + * @since 1.19 + */ + EINA_FREEQ_POSTPONED, +} Eina_FreeQ_Type; + /** * @brief Create a new free queue to defer freeing of data with * @@ -83,7 +126,7 @@ typedef struct _Eina_FreeQ Eina_FreeQ; * @since 1.19 */ EAPI Eina_FreeQ * -eina_freeq_new(void); +eina_freeq_new(Eina_FreeQ_Type type); /** * @brief Free a free queue and anything that is queued in it. @@ -95,6 +138,16 @@ eina_freeq_new(void); EAPI void eina_freeq_free(Eina_FreeQ *fq); +/** + * @brief Query the type of a free queue. + * + * @param fq The free queue to inspect. + * + * @since 1.19 + */ +EAPI Eina_FreeQ_Type +eina_freeq_type_get(Eina_FreeQ *fq); + /** * @brief Set the main free queue driven by the EFL mainloop. * @@ -119,14 +172,18 @@ 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) + * @param count The maximum number of items allowed, negative values mean + * no limit * * 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. - * + * + * @note Setting a maximum count on a postponed free queue leads to undefined + * behaviour. + * * @since 1.19 */ EAPI void @@ -136,7 +193,7 @@ 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 + * @return The maximum number of free items allowed or -1 for infinity * * @since 1.19 */ @@ -156,6 +213,9 @@ eina_freeq_count_max_get(Eina_FreeQ *fq); * 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. + * + * @note Setting a memory limit on a postponed free queue leads to undefined + * behaviour. * * @since 1.19 */ diff --git a/src/lib/eina/eina_main.c b/src/lib/eina/eina_main.c index 2f754e5360..ab0aec90b1 100644 --- a/src/lib/eina/eina_main.c +++ b/src/lib/eina/eina_main.c @@ -255,7 +255,7 @@ eina_init(void) mtrace(); } #endif - eina_freeq_main_set(eina_freeq_new()); + eina_freeq_main_set(eina_freeq_new(EINA_FREEQ_DEFAULT)); if (!eina_log_init()) { diff --git a/src/tests/eina/eina_test_freeq.c b/src/tests/eina/eina_test_freeq.c index bf732b9be0..c1d050a726 100644 --- a/src/tests/eina/eina_test_freeq.c +++ b/src/tests/eina/eina_test_freeq.c @@ -14,8 +14,9 @@ START_TEST(freeq_simple) fail_if(eina_freeq_main_get() == NULL); pfq = eina_freeq_main_get(); + fail_if(eina_freeq_type_get(pfq) != EINA_FREEQ_DEFAULT); - fq = eina_freeq_new(); + fq = eina_freeq_new(EINA_FREEQ_DEFAULT); fail_if(!fq); eina_freeq_main_set(fq); @@ -113,10 +114,91 @@ START_TEST(freeq_reduce) } END_TEST +static void +postponed_free(void *data) +{ + int *p = data; + + // we leak here (by choice -- to inspect the memory after clear/reduce) + *p = 0xDEADBEEF; + _n--; +} + +static inline unsigned int * +new_uint(int val) +{ + unsigned int *p; + + p = malloc(sizeof(*p)); + *p = val; + return p; +} + +START_TEST(freeq_postponed) +{ + Eina_FreeQ *fq; + unsigned int *values[20]; + size_t k; + + eina_init(); + _n = 0; + + fq = eina_freeq_new(EINA_FREEQ_POSTPONED); + + fail_if(!fq); + fail_if(eina_freeq_type_get(fq) != EINA_FREEQ_POSTPONED); + + // by default: no limit + ck_assert_int_eq(eina_freeq_count_max_get(fq), -1); + ck_assert_int_eq(eina_freeq_mem_max_get(fq), 0); + + for (k = 0; k < EINA_C_ARRAY_LENGTH(values); k++) + { + _n++; + values[k] = new_uint(k); + eina_freeq_ptr_add(fq, values[k], postponed_free, sizeof(int)); + } + ck_assert_int_eq(_n, EINA_C_ARRAY_LENGTH(values)); + + fail_if(!eina_freeq_ptr_pending(fq)); + while (eina_freeq_ptr_pending(fq)) + eina_freeq_reduce(fq, 1); + fail_if(eina_freeq_ptr_pending(fq)); + ck_assert_int_eq(_n, 0); + + for (k = 0; k < EINA_C_ARRAY_LENGTH(values); k++) + ck_assert_int_eq(*(values[k]), 0xDEADBEEF); + + for (k = 0; k < EINA_C_ARRAY_LENGTH(values); k++) + { + _n++; + values[k] = new_uint(k); + eina_freeq_ptr_add(fq, values[k], postponed_free, sizeof(int)); + } + ck_assert_int_eq(_n, EINA_C_ARRAY_LENGTH(values)); + + fail_if(!eina_freeq_ptr_pending(fq)); + eina_freeq_clear(fq); + fail_if(eina_freeq_ptr_pending(fq)); + ck_assert_int_eq(_n, 0); + + for (k = 0; k < EINA_C_ARRAY_LENGTH(values); k++) + { + ck_assert_int_eq(*(values[k]), 0xDEADBEEF); + free(values[k]); + } + + eina_freeq_free(fq); + + 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); + tcase_add_test(tc, freeq_postponed); }