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)
};
// ========================================================================= //
// ========================================================================= //
+#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)
{
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;
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)
{
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
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;
}
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
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;
}
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
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;
}
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;
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);
}
* 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
*
*/
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
*
* @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.
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.
*
* @param fq The free queue to set as the main loop one.
* @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
* @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
*/
* 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
*/
mtrace();
}
#endif
- eina_freeq_main_set(eina_freeq_new());
+ eina_freeq_main_set(eina_freeq_new(EINA_FREEQ_DEFAULT));
if (!eina_log_init())
{
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);
}
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);
}