eina: Introduce Eina_Slstr for short-lived strings

Built on top of the new 'postponed' free queue, the short-lived
strings API allows users to return new strings without caring
about freeing them. EFL main loop will do this automatically for
them you at a later point in time (at the end of an iteration).

The APIs provided will either duplicate (copy) or more generally
steal an existing string (char *, stringshare, tmpstr, strbuf),
taking ownership of it and controling its lifetime. Those strings
can then be safely returned by an API. From a user point of view,
those strings must be considered like simple const char *, ie.
no need to free() them and their validity is limited to the
local scope.

There is no function to remove such a string from the freeq.

The short lived strings API is not thread-safe: do not send a
short-lived object from one thread to another.

@feature
This commit is contained in:
Jean-Philippe Andre 2017-01-11 15:34:15 +09:00
parent 4f5e64fdea
commit 4550b4cf83
10 changed files with 623 additions and 4 deletions

View File

@ -103,7 +103,8 @@ 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_freeq.h
lib/eina/eina_freeq.h \
lib/eina/eina_slstr.h
lib_eina_libeina_la_SOURCES = \
@ -177,7 +178,8 @@ lib/eina/eina_strbuf_common.h \
lib/eina/eina_quaternion.c \
lib/eina/eina_bezier.c \
lib/eina/eina_safepointer.c \
lib/eina/eina_freeq.c
lib/eina/eina_freeq.c \
lib/eina/eina_slstr.c
if HAVE_WIN32
@ -348,7 +350,8 @@ tests/eina/eina_test_vector.c \
tests/eina/eina_test_bezier.c \
tests/eina/eina_test_safepointer.c \
tests/eina/eina_test_slice.c \
tests/eina/eina_test_freeq.c
tests/eina/eina_test_freeq.c \
tests/eina/eina_test_slstr.c
tests_eina_eina_suite_CPPFLAGS = -I$(top_builddir)/src/lib/efl \
-DTESTS_WD=\"`pwd`\" \

View File

@ -2,6 +2,8 @@
# include <config.h>
#endif
#define EINA_SLSTR_INTERNAL
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <winsock2.h>
@ -2388,6 +2390,11 @@ process_all: /*-*********************************************************/
done: /*-*****************************************************************/
/* Agressively flush animator */
_ecore_animator_flush();
if (!once_only)
{
/* Free all short lived strings */
eina_slstr_local_clear();
}
in_main_loop--;
}

View File

@ -271,6 +271,7 @@ extern "C" {
#include <eina_safepointer.h>
#include <eina_slice.h>
#include <eina_freeq.h>
#include <eina_slstr.h>
#undef EAPI
#define EAPI

View File

@ -89,7 +89,8 @@ typedef enum _Eina_FreeQ_Type
* immediately. Use this kind of freeq for debugging and additional memory
* safety purposes only.
*
* This type of free queue is thread-safe.
* As this type of free queue is thread-safe, the free functions used must
* also be thread-safe (eg. libc free()).
*
* @since 1.19
*/

View File

@ -69,6 +69,7 @@
#include "eina_value.h"
#include "eina_evlog.h"
#include "eina_freeq.h"
#include "eina_slstr.h"
/*============================================================================*
* Local *
@ -153,6 +154,7 @@ EAPI Eina_Inlist *_eina_tracking = NULL;
S(rbtree);
S(file);
S(safepointer);
S(slstr);
#undef S
struct eina_desc_setup
@ -198,6 +200,7 @@ static const struct eina_desc_setup _eina_desc_setup[] = {
S(rbtree),
S(file),
S(safepointer),
S(slstr),
#undef S
};
static const size_t _eina_desc_setup_len = sizeof(_eina_desc_setup) /

215
src/lib/eina/eina_slstr.c Normal file
View File

@ -0,0 +1,215 @@
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#define EINA_SLSTR_INTERNAL
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Eina.h"
#include "eina_private.h"
// ========================================================================= //
static int _slstr_init = 0;
static Eina_FreeQ *_slstr_main_fq = NULL;
static Eina_TLS _slstr_tls = 0;
// ========================================================================= //
#if 0
// 2 extension ideas here: slices for short-lived raw data buffers
EAPI Eina_Rw_Slice eina_slslice_new(size_t length); // alloc
EAPI Eina_Rw_Slice eina_slslice_copy(Eina_Slice slice); // copies
EAPI Eina_Rw_Slice eina_slslice_free(Eina_Rw_Slice slice); // steals
#endif
static void
_slstr_tls_free_cb(void *ptr)
{
Eina_FreeQ *fq = ptr;
eina_freeq_free(fq);
}
Eina_Bool
eina_slstr_init(void)
{
if (_slstr_init++) return EINA_TRUE;
_slstr_main_fq = eina_freeq_new(EINA_FREEQ_POSTPONED);
if (!_slstr_main_fq) goto fail;
if (!eina_tls_cb_new(&_slstr_tls, _slstr_tls_free_cb)) goto fail_tls;
return EINA_TRUE;
fail_tls:
eina_tls_free(_slstr_tls);
_slstr_tls = 0;
fail:
eina_freeq_free(_slstr_main_fq);
_slstr_main_fq = NULL;
return EINA_FALSE;
}
Eina_Bool
eina_slstr_shutdown(void)
{
if (_slstr_init == 0) return EINA_FALSE;
if (--_slstr_init) return EINA_TRUE;
eina_freeq_free(_slstr_main_fq);
eina_tls_free(_slstr_tls);
_slstr_main_fq = NULL;
_slstr_tls = 0;
return EINA_TRUE;
}
static inline Eina_FreeQ *
_slstr_freeq_get(Eina_Bool nocreate)
{
if (eina_main_loop_is())
return _slstr_main_fq;
else
{
Eina_FreeQ *fq;
fq = eina_tls_get(_slstr_tls);
if (!nocreate && EINA_UNLIKELY(!fq))
{
fq = eina_freeq_new(EINA_FREEQ_POSTPONED);
eina_tls_set(_slstr_tls, fq);
}
return fq;
}
}
EAPI Eina_Slstr *
eina_slstr_copy_new(const char *string)
{
Eina_FreeQ *fq;
char *copy;
size_t len = 0;
if (!string) return NULL;
fq = _slstr_freeq_get(EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(fq, NULL);
copy = eina_strdup(string);
if (!copy) return NULL;
#ifdef DEBUG
len = strlen(string) + 1;
#endif
eina_freeq_ptr_add(fq, copy, free, len);
return copy;
}
EAPI Eina_Slstr *
eina_slstr_steal_new(char *string)
{
Eina_FreeQ *fq;
size_t len = 0;
if (!string) return NULL;
fq = _slstr_freeq_get(EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(fq, NULL);
#ifdef DEBUG
len = strlen(string) + 1;
#endif
eina_freeq_ptr_add(fq, string, free, len);
return string;
}
EAPI Eina_Slstr *
eina_slstr_stringshare_new(Eina_Stringshare *string)
{
Eina_FreeQ *fq;
size_t len = 0;
if (!string) return NULL;
fq = _slstr_freeq_get(EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(fq, NULL);
#ifdef DEBUG
len = eina_stringshare_strlen(string) + 1;
#endif
eina_freeq_ptr_add(fq, (void *) string, EINA_FREE_CB(eina_stringshare_del), len);
return string;
}
EAPI Eina_Slstr *
eina_slstr_tmpstr_new(Eina_Tmpstr *string)
{
Eina_FreeQ *fq;
size_t len = 0;
if (!string) return NULL;
fq = _slstr_freeq_get(EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(fq, NULL);
#ifdef DEBUG
len = eina_tmpstr_strlen(string) + 1;
#endif
eina_freeq_ptr_add(fq, (void *) string, EINA_FREE_CB(eina_tmpstr_del), len);
return string;
}
EAPI Eina_Slstr *
eina_slstr_strbuf_new(Eina_Strbuf *string)
{
Eina_FreeQ *fq;
size_t len = 0;
char *str;
if (!string) return NULL;
fq = _slstr_freeq_get(EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(fq, NULL);
str = eina_strbuf_release(string);
#ifdef DEBUG
len = eina_strbuf_length_get(string) + 1;
#endif
eina_freeq_ptr_add(fq, str, free, len);
return str;
}
EAPI Eina_Slstr *
eina_slstr_vasprintf_new(const char *fmt, va_list args)
{
Eina_FreeQ *fq;
size_t len = 0;
char *str;
int r;
fq = _slstr_freeq_get(EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(fq, NULL);
r = vasprintf(&str, fmt, args);
if (r == -1) return NULL;
#ifdef DEBUG
len = r + 1;
#endif
eina_freeq_ptr_add(fq, str, free, len);
return str;
}
EAPI void
eina_slstr_local_clear(void)
{
Eina_FreeQ *fq;
fq = _slstr_freeq_get(EINA_TRUE);
if (!fq) return;
eina_freeq_clear(fq);
}

179
src/lib/eina/eina_slstr.h Normal file
View File

@ -0,0 +1,179 @@
#ifndef EINA_SLSTR_H_
#define EINA_SLSTR_H_
#include <stdlib.h>
#include "eina_config.h"
#include "eina_types.h"
#include "eina_tmpstr.h"
#include "eina_strbuf.h"
#include "eina_stringshare.h"
#include "eina_slice.h"
/**
* @addtogroup Eina_Slstr Short lived strings
* @ingroup Eina
*
* @brief API for short lived strings (thread- and scope-local)
*
* This set of APIs provide a convenience feature to create and return strings
* that are meant to be consumed in the local scope of the calling code block.
* The lifecycle of those strings is bound to the loop of the current thread
* or until the clear function is called explicitely.
*
* These strings will be automatically deleted.
*
* These functions shall return NULL only if out of memory.
*
* Do not call free or any similar function on a string created with this API!
*
* @since 1.19
*/
typedef const char Eina_Slstr;
/**
* @brief Create a new short lived string by duplicating another string.
*
* @param string An existing string, it will be copied.
* @return A new Eina_Slstr or NULL if out of memory.
*
* Usage example:
* @code
* char local[200];
* sprintf(local, "Hello %d", value);
* return eina_slstr_copy_new(local);
* @endcode
*
* @since 1.19
*/
EAPI Eina_Slstr *
eina_slstr_copy_new(const char *string);
/**
* @brief Create a new short lived string by taking ownership of a string.
*
* @param string An existing string. It will not be duplicated.
* @return A new Eina_Slstr or NULL if out of memory.
*
* Usage example:
* @code
* char *local = strdup("Hello");
* return eina_slstr_steal_new(local);
* @endcode
*
* @since 1.19
*/
EAPI Eina_Slstr *
eina_slstr_steal_new(char *string);
/**
* @brief Create a new short lived string by taking ownership of a stringshare.
*
* @param string An existing stringshare, one reference belongs to this slstr.
* @return A new Eina_Slstr or NULL if out of memory.
*
* Usage example:
* @code
* Eina_Stringshare *local = eina_stringshare_add("Hello");
* return eina_slstr_stringshare_new(local);
* @endcode
*
* @since 1.19
*/
EAPI Eina_Slstr *
eina_slstr_stringshare_new(Eina_Stringshare *string);
/**
* @brief Create a new short lived string by taking ownership of a tmpstr.
*
* @param string An existing tmpstr, it will be freed later.
* @return A new Eina_Slstr or NULL if out of memory.
*
* Usage example:
* @code
* Eina_Tmpstr *local = eina_tmpstr_add("Hello");
* return eina_slstr_tmpstr_new(local);
* @endcode
*
* @since 1.19
*/
EAPI Eina_Slstr *
eina_slstr_tmpstr_new(Eina_Tmpstr *string);
/**
* @brief Create a new short lived string by taking ownership of a strbuf.
*
* @param string An existing strbuf, that will be released (ie. steal + free).
* @return A new Eina_Slstr or NULL if out of memory.
*
* Usage example:
* @code
* Eina_Strbuf *local = eina_strbuf_new();
* eina_strbuf_append(local, "Hello");
* eina_strbuf_append(local, " world");
* return eina_slstr_strbuf_new(local);
* @endcode
*
* @note Use eina_slstr_steal_new() if the strbuf will be used after this call.
*
* @since 1.19
*/
EAPI Eina_Slstr *
eina_slstr_strbuf_new(Eina_Strbuf *string);
/**
* @brief Create a new short lived string using sprintf.
*
* @param fmt Format string for printf
* @param args List of format parameters for printf
* @return A new Eina_Slstr or NULL if out of memory.
*
* @since 1.19
*/
EAPI Eina_Slstr *
eina_slstr_vasprintf_new(const char *fmt, va_list args);
/**
* @brief Create a new short lived string using sprintf.
*
* @param fmt Format string for printf
* @param args List of format parameters for printf
* @return A new Eina_Slstr or NULL if out of memory.
*
* Usage example:
* @code
* return eina_slstr_printf("Hello world %d!", 42);
* @endcode
*
* @since 1.19
*/
static inline Eina_Slstr *
eina_slstr_printf(const char *fmt, ...)
{
Eina_Slstr *str;
va_list args;
va_start(args, fmt);
str = eina_slstr_vasprintf_new(fmt, args);
va_end(args);
return str;
}
#ifdef EINA_SLSTR_INTERNAL
/**
* @brief Internal function to clear the strings.
*
* This internal function will be called by the local thread's loop to free
* all the strings. Do not call this function unless you are absolutely certain
* that no string in the queue will be used after this point.
*
* @since 1.19
*/
EAPI void
eina_slstr_local_clear(void);
#endif
#endif

View File

@ -86,6 +86,7 @@ static const Efl_Test_Case etc[] = {
{ "Slice", eina_test_slice },
{ "Free Queue", eina_test_freeq },
{ "Util", eina_test_util },
{ "Short Lived Strings", eina_test_slstr },
{ NULL, NULL }
};

View File

@ -73,5 +73,6 @@ void eina_test_bezier(TCase *tc);
void eina_test_safepointer(TCase *tc);
void eina_test_slice(TCase *tc);
void eina_test_freeq(TCase *tc);
void eina_test_slstr(TCase *tc);
#endif /* EINA_SUITE_H_ */

View File

@ -0,0 +1,208 @@
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#define EINA_SLSTR_INTERNAL
#include <Eina.h>
#include "eina_suite.h"
static Eina_Slstr *
_slstr_copy(void)
{
const char local[] = "Hello world 1";
return eina_slstr_copy_new(local);
}
START_TEST(slstr_copy)
{
Eina_Slstr *str;
eina_init();
str = _slstr_copy();
ck_assert_str_eq(str, "Hello world 1");
eina_shutdown();
}
END_TEST
static Eina_Slstr *
_slstr_steal(void)
{
char *local = strdup("Hello world 2");
return eina_slstr_copy_new(local);
}
START_TEST(slstr_steal)
{
Eina_Slstr *str;
eina_init();
str = _slstr_steal();
ck_assert_str_eq(str, "Hello world 2");
eina_shutdown();
}
END_TEST
static Eina_Slstr *
_slstr_stringshare(void)
{
Eina_Stringshare *str = eina_stringshare_add("Hello world 3");
return eina_slstr_stringshare_new(str);
}
START_TEST(slstr_stringshare)
{
Eina_Stringshare *ss;
Eina_Slstr *str;
eina_init();
str = _slstr_stringshare();
ss = eina_stringshare_add("Hello world 3");
fail_if(ss != str);
eina_shutdown();
}
END_TEST
static Eina_Slstr *
_slstr_tmpstr(void)
{
Eina_Tmpstr *str = eina_tmpstr_add("Hello world 4");
return eina_slstr_tmpstr_new(str);
}
START_TEST(slstr_tmpstr)
{
Eina_Slstr *str;
eina_init();
str = _slstr_tmpstr();
ck_assert_str_eq(str, "Hello world 4");
eina_shutdown();
}
END_TEST
static Eina_Slstr *
_slstr_strbuf(void)
{
Eina_Strbuf *str = eina_strbuf_new();
eina_strbuf_append(str, "Hello ");
eina_strbuf_append(str, "world ");
eina_strbuf_append_printf(str, "%d", 5);
return eina_slstr_strbuf_new(str);
}
START_TEST(slstr_strbuf)
{
Eina_Slstr *str;
eina_init();
str = _slstr_strbuf();
ck_assert_str_eq(str, "Hello world 5");
eina_shutdown();
}
END_TEST
static Eina_Slstr *
_slstr_printf(int val)
{
return eina_slstr_printf("Hello %s %d", "world", val);
}
START_TEST(slstr_slstr_printf)
{
Eina_Slstr *str;
eina_init();
str = _slstr_printf(6);
ck_assert_str_eq(str, "Hello world 6");
eina_shutdown();
}
END_TEST
static void
_many_do(void)
{
const int many = 2048;
Eina_Slstr *str;
int k;
for (k = 0; k < many; k++)
{
char local[64];
str = _slstr_printf(k);
sprintf(local, "Hello world %d", k);
ck_assert_str_eq(str, local);
}
}
START_TEST(slstr_many)
{
eina_init();
_many_do();
eina_slstr_local_clear();
eina_shutdown();
}
END_TEST
static void *
_thread_cb(void *data EINA_UNUSED, Eina_Thread th EINA_UNUSED)
{
_many_do();
return NULL;
}
START_TEST(slstr_thread)
{
const int threads = 8;
Eina_Thread th[threads];
int k;
eina_init();
for (k = 0; k < threads; k++)
fail_if(!eina_thread_create(&th[k], EINA_THREAD_NORMAL, -1, _thread_cb, NULL));
for (k = 0; k < threads; k++)
eina_thread_join(th[k]);
eina_slstr_local_clear();
eina_shutdown();
}
END_TEST
void
eina_test_slstr(TCase *tc)
{
tcase_add_test(tc, slstr_copy);
tcase_add_test(tc, slstr_steal);
tcase_add_test(tc, slstr_stringshare);
tcase_add_test(tc, slstr_tmpstr);
tcase_add_test(tc, slstr_strbuf);
tcase_add_test(tc, slstr_slstr_printf);
tcase_add_test(tc, slstr_many);
tcase_add_test(tc, slstr_thread);
}