Compare commits

...

13 Commits

Author SHA1 Message Date
Gustavo Sverzut Barbieri 306ec6937b Efl.Loop.coro: easy to use coroutines attached to a main loop.
While Eina_Coro provides a solid base, to use the main loop to
schedule coroutines it needs some manual work we want to avoid.

Efl.Loop.coro method will take a function and schedule it using the
given priority, the returned value is then resolved in a promise for
the returned future.

Basically all users must do is write a function that looks like a
synchronous code and calls eina_coro_yield() (or helper macros), that
will go back to the main loop and then it will reschedule the
coroutine to run according to its priority.

This should reduce the number of callbacks in user's code.
2017-08-27 11:47:55 -03:00
Gustavo Sverzut Barbieri 109bf1b387 Efl_Object: make main domain accessible in Eina_Coro.
Currently Eina_Coro is implemented with actual threads, that barf Eo
checkings and makes main thread objects unusuable.

Since this is an implementation detail, let's use Eina_Coro hooks to
adopt the main domain and return when it's done... The user doesn't
see this, it's transparent.
2017-08-27 11:47:55 -03:00
Gustavo Sverzut Barbieri d10a35628c eina: add Eina_Coro - coroutine support.
Coroutines are cooperative tasks, in the sense that the caller will
stop until the target function runs. The target function must either
yield control back to the caller (main thread), or exit. There is no
preemption of the two tasks, thus no special care needs to be taken
regarding shared data.

If the target coroutine yields control using eina_coro_yield(), then
it will be paused until it's manually ran again by caller (main
thread), which is executed with eina_coro_run().

Another common usage is to await for another task to be completed,
this can be done by waiting for a future to be resolved. It will
automatically yield and inform the caller of the future so it can
schedule properly instead of keep calling the task. Waiting for many
tasks can be achieved by using eina_future_all() or
eina_future_race(). This is done with eina_coro_await().

Due portability it was implemented using Eina_Thread, Eina_Lock and
Eina_Condition. Regular threads will ensure that the state is fully
preserved (stack, registers) in a platform independent way. Each
thread will wait on its own turn using the Eina_Lock and
Eina_Condition, thus it's guaranteed that only one is being executed
at the same time.

The API is small and should allow different implementations shall we
need them, like manually saving the stack and registers, then
restoring those -- problem is doing that in a portable way,
setjmp()/longjmp() won't save the stack, makecontext()/swapcontext()
doesn't work right on MacOS...

Hooks can be used to be informed when the main routine exits and then
enters, likewise when the coroutine enters and exits. These will be
used, for instance, to automatically get, adopt and return
Efl_Domain_Data needed to make Efl_Object work in such
environment. The flow is simple:

  - main exit (called from main thread)
  - coroutine enter (called from worker thread)
  - coroutine exit (called from worker thread)
  - main enter (called from main thead)

Performance may not be optimal, however this is meant as easy-to-use
and it shouldn't be an issue in real life. It will be mostly exposed
in two layers:

 - Efl.Loop.coro: will wrap eina_coro and and schedule using its main
   loop instance, returns an Eina_Future so it's easy to chain.

 - Eina_Promise/Eina_Future "async/await"-like behavior: will allow to
   write "synchronous" code that can wait for promises to be
   resolved. When eina_future_await(), it will actually register a new
   Eina_Future in the chain and then eina_coro_yield(). Once the
   future is called back it will call eina_coro_run() and allow the
   coroutine to resume. This is done on top fo eina_coro_await().
2017-08-27 11:47:55 -03:00
Gustavo Sverzut Barbieri 4d7661b174 Efl_Loop: add job, timeout and idle based on Eina_Future.
Since some clash with old version, then add Eina_FutureXXX to their
name, later we'll sed.
2017-08-27 11:47:55 -03:00
Gustavo Sverzut Barbieri f3d5642503 export efl_future_then() for Eina_Future syntax sugar.
This is actually written as efl_future_Eina_FutureXXX_then() as the
old API clashes, after removing the old code we'll "sed" to fix those.
2017-08-27 11:35:38 -03:00
Gustavo Sverzut Barbieri 7dc41ab0e5 Eina_Future: add eina_future_resolved()
This is a helper that creates a promise, then a future and immediately
resolves the promise, this will let the future be dispatched as usual,
from a clean main loop context.
2017-08-27 11:35:38 -03:00
Gustavo Sverzut Barbieri 1a0b921789 Eina_Future: add to eina_types.eot 2017-08-27 11:35:38 -03:00
Gustavo Sverzut Barbieri 701aab803d fixup Efl_Object: Add integration with Eina_Future.
Rename _from_array() to _array(), following others.

Fix variable to be signed so -1 is clean (it would work due overlow,
but not that clear).
2017-08-27 11:35:38 -03:00
Gustavo Sverzut Barbieri b2cd2cbf16 fixup Eina: Add Eina_Promise/Eina_Future (convert_to)
convert_to() should just setup for empty, getting the default value.

on errors it should convert to EINA_VALUE_TYPE_ERROR, otherwise pass
thru.
2017-08-27 11:35:38 -03:00
Gustavo Sverzut Barbieri 07f272b52e fixup Eina: Add Eina_Promise/Eina_Future
_eina_promise_clean_dispatch() must call _eina_future_dispatch() since
complex chains will need to be walked -- this bugged me for some
complex tests I'm doing with coroutines.

Unfortunately that will modify the value and cause problems since the
caller trust it wasn't modified and could cause double-free or invalid
memory access. Then we must copy it in _future_proxy().

Other usage for _eina_promise_clean_dispatch() were leaking and are
now "auto fixed".
2017-08-27 11:35:38 -03:00
Guilherme Iscaro c35e929713 Add new Future/Promise API.
Summary:
Eina: Add Eina_Promise/Eina_Future.

This commit adds a new promise/future API which aims to replace
efl_future.

Efl_Object: Add integration with Eina_Future.

This commit adds the EO support for the new future infra.
From now on there's no need to efl_future_link()/efl_future_unlink()
object and futures since the new API already handles that internally.

Eina_Promise/Eina_Future: Add example and tests.

Subscribers: cedric, jpeg

Differential Revision: https://phab.enlightenment.org/D5131
2017-08-25 20:42:23 -03:00
Guilherme Iscaro 0dd2c1a530 Efl_Object: Add integration with Eina_Future.
This commit adds the EO support for the new future infra.
From now on there's no need to efl_future_link()/efl_future_unlink()
object and futures since the new API already handles that internally.
2017-08-25 19:53:49 -03:00
Guilherme Iscaro d30c0d4f03 Eina: Add Eina_Promise/Eina_Future.
This commit adds a new promise/future API which aims to replace
efl_future.
2017-08-25 19:53:49 -03:00
31 changed files with 7231 additions and 23 deletions

View File

@ -226,6 +226,7 @@ tests/ecore/ecore_test_ecore_thread_eina_thread_queue.c \
tests/ecore/ecore_test_ecore_input.c \
tests/ecore/ecore_test_ecore_file.c \
tests/ecore/ecore_test_promise.c \
tests/ecore/ecore_test_promise2.c \
tests/ecore/ecore_test_job.c \
tests/ecore/ecore_test_args.c \
tests/ecore/ecore_suite.h

View File

@ -10,6 +10,7 @@ lib/eina/eina_config.h
installed_einaheadersdir = $(includedir)/eina-@VMAJ@/eina
dist_installed_einaheaders_DATA = \
lib/eina/eina_promise.h \
lib/eina/eina_safety_checks.h \
lib/eina/eina_error.h \
lib/eina/eina_debug.h \
@ -106,7 +107,8 @@ 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_slstr.h
lib/eina/eina_slstr.h \
lib/eina/eina_coro.h
lib_eina_libeina_la_SOURCES = \
@ -150,6 +152,8 @@ lib/eina/eina_mempool.c \
lib/eina/eina_mmap.c \
lib/eina/eina_module.c \
lib/eina/eina_prefix.c \
lib/eina/eina_promise.c \
lib/eina/eina_promise_private.h \
lib/eina/eina_quad.c \
lib/eina/eina_quadtree.c \
lib/eina/eina_rbtree.c \
@ -180,7 +184,8 @@ lib/eina/eina_quaternion.c \
lib/eina/eina_bezier.c \
lib/eina/eina_safepointer.c \
lib/eina/eina_freeq.c \
lib/eina/eina_slstr.c
lib/eina/eina_slstr.c \
lib/eina/eina_coro.c
if HAVE_WIN32
@ -353,7 +358,8 @@ 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_slstr.c
tests/eina/eina_test_slstr.c \
tests/eina/eina_test_coro.c
tests_eina_eina_suite_CPPFLAGS = -I$(top_builddir)/src/lib/efl \
-DTESTS_WD=\"`pwd`\" \

View File

@ -146,6 +146,7 @@ tests/eo/suite/eo_test_class_behaviour_errors.c \
tests/eo/suite/eo_test_call_errors.c \
tests/eo/suite/eo_test_general.c \
tests/eo/suite/eo_test_value.c \
tests/eo/suite/eo_test_coro.c \
tests/eo/suite/eo_test_event.c \
tests/eo/suite/eo_test_threaded_calls.c \
tests/eo/suite/eo_test_init.c

View File

@ -76,6 +76,7 @@ ecore_idler_example \
ecore_imf_example \
ecore_job_example \
ecore_poller_example \
ecore_promise2_example \
ecore_server_bench \
ecore_thread_example \
ecore_time_functions_example \
@ -292,6 +293,9 @@ endif
ecore_poller_example_SOURCES = ecore_poller_example.c
ecore_poller_example_LDADD = $(ECORE_COMMON_LDADD)
ecore_promise2_example_SOURCES = ecore_promise2_example.c
ecore_promise2_example_LDADD = $(ECORE_COMMON_LDADD)
ecore_server_bench_SOURCES = ecore_server_bench.c
ecore_server_bench_LDADD = $(ECORE_CON_COMMON_LDADD)
@ -425,6 +429,7 @@ ecore_job_example.c \
ecore_pipe_gstreamer_example.c \
ecore_pipe_simple_example.c \
ecore_poller_example.c \
ecore_promise2_example.c \
ecore_server_bench.c \
ecore_thread_example.c \
ecore_time_functions_example.c \

View File

@ -0,0 +1,385 @@
#define EFL_EO_API_SUPPORT 1
#define EFL_BETA_API_SUPPORT 1
#include <Ecore.h>
#include <Eina.h>
#include <stdlib.h>
#include <errno.h>
typedef struct _Ctx {
Eina_Promise *p;
Eina_Bool should_fail;
Ecore_Timer *timer;
Eina_Value *value;
} Ctx;
typedef struct _Inner_Promise_Ctx {
Eina_Future *future;
Eina_Promise *promise;
} Inner_Promise_Ctx;
#define DEFAULT_MSG "the simple example is working!"
#define VALUE_TYPE_CHECK(_v, _type) \
if (_v.type != _type) \
{ \
fprintf(stderr, "Value type is not '%s' - received '%s'\n", \
_type->name, _v.type->name); \
return _v; \
}
static Eina_Bool
_timeout(void *data)
{
Ctx *ctx = data;
if (ctx->should_fail) eina_promise_reject(ctx->p, ENETDOWN);
else
{
Eina_Value v;
eina_value_copy(ctx->value, &v);
eina_promise_resolve(ctx->p, v);
eina_value_free(ctx->value);
}
free(ctx);
return EINA_FALSE;
}
static void
_promise_cancel(void *data, const Eina_Promise *dead EINA_UNUSED)
{
Ctx *ctx = data;
if (ctx->timer) ecore_timer_del(ctx->timer);
eina_value_free(ctx->value);
free(ctx);
}
static Eina_Future_Scheduler *
_future_scheduler_get(void)
{
return efl_loop_future_scheduler_get(ecore_main_loop_get());
}
static Ctx *
_promise_ctx_new(Eina_Value *v)
{
Ctx *ctx;
ctx = calloc(1, sizeof(Ctx));
EINA_SAFETY_ON_NULL_GOTO(ctx, err_ctx);
ctx->p = eina_promise_new(_future_scheduler_get(), _promise_cancel, ctx);
EINA_SAFETY_ON_NULL_GOTO(ctx->p, err_timer);
ctx->value = v;
return ctx;
err_timer:
free(ctx);
err_ctx:
eina_value_free(v);
return NULL;
}
static Eina_Future *
_future_get(Ctx *ctx)
{
Eina_Future *f;
f = eina_future_new(ctx->p);
EINA_SAFETY_ON_NULL_GOTO(f, err_future);
ctx->timer = ecore_timer_add(0.1, _timeout, ctx);
EINA_SAFETY_ON_NULL_GOTO(ctx->timer, err_timer);
return f;
err_timer:
eina_future_cancel(f);
err_future:
return NULL;
}
static Eina_Future *
_fail_future_get(void)
{
Ctx *ctx = _promise_ctx_new(NULL);
EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
ctx->should_fail = EINA_TRUE;
return _future_get(ctx);
}
static Eina_Future *
_str_future_get(void)
{
Eina_Value *v = eina_value_util_string_new(DEFAULT_MSG);
EINA_SAFETY_ON_NULL_RETURN_VAL(v, NULL);
Ctx *ctx = _promise_ctx_new(v);
EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
return _future_get(ctx);
}
static Eina_Future *
_int_future_get(void)
{
Eina_Value *v= eina_value_util_int_new(0);
EINA_SAFETY_ON_NULL_RETURN_VAL(v, NULL);
Ctx *ctx = _promise_ctx_new(v);
EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
return _future_get(ctx);
}
static Eina_Value
_simple_ok(void *data EINA_UNUSED, const Eina_Value v, const Eina_Future *dead_future EINA_UNUSED)
{
VALUE_TYPE_CHECK(v, EINA_VALUE_TYPE_STRING);
return v;
}
static Eina_Value
_alternate_error_cb(void *data, const Eina_Value v, const Eina_Future *dead_future EINA_UNUSED)
{
Eina_Bool *should_fail = data;
Eina_Value new_v = EINA_VALUE_EMPTY;
if (*should_fail)
{
*should_fail = EINA_FALSE;
eina_value_setup(&new_v, EINA_VALUE_TYPE_ERROR);
eina_value_set(&new_v, ENETDOWN);
printf("Received succes from the previous future - Generating error for the next future...\n");
}
else
{
*should_fail = EINA_TRUE;
Eina_Error err;
VALUE_TYPE_CHECK(v, EINA_VALUE_TYPE_ERROR);
eina_value_get(&v, &err);
printf("Received error from the previous future - value: %s. Send success\n",
eina_error_msg_get(err));
}
return new_v;
}
static void
_alternate_error(void)
{
static Eina_Bool should_fail = EINA_TRUE;
eina_future_chain(_str_future_get(),
{.cb = _alternate_error_cb, .data = &should_fail},
{.cb = _alternate_error_cb, .data = &should_fail},
{.cb = _alternate_error_cb, .data = &should_fail},
{.cb = _alternate_error_cb, .data = &should_fail});
}
static Eina_Value
_simple_err(void *data EINA_UNUSED, const Eina_Value v, const Eina_Future *dead_future EINA_UNUSED)
{
VALUE_TYPE_CHECK(v, EINA_VALUE_TYPE_ERROR);
return v;
}
static void
_simple(void)
{
eina_future_chain(_str_future_get(),
eina_future_cb_console("Expecting the following message: "DEFAULT_MSG ". Got: ", NULL),
{ .cb = _simple_ok, .data = NULL });
eina_future_chain(_fail_future_get(),
eina_future_cb_console("Expectig network down error. Got: ", NULL),
{ .cb = _simple_err, .data = NULL });
}
static Eina_Value
_chain_no_errors_cb(void *data EINA_UNUSED, const Eina_Value v, const Eina_Future *dead_future EINA_UNUSED)
{
int count;
Eina_Value new_v;
eina_value_setup(&new_v, EINA_VALUE_TYPE_INT);
if (!v.type)
count = 1;
else
{
VALUE_TYPE_CHECK(v, EINA_VALUE_TYPE_INT);
eina_value_get(&v, &count);
}
eina_value_set(&new_v, count * 2);
return new_v;
}
static void
_chain_no_errors(void)
{
eina_future_chain(_int_future_get(),
eina_future_cb_console("Expecting no value. Got: ", NULL),
{.cb = _chain_no_errors_cb, .data = NULL},
eina_future_cb_console("Expecting number 2. Got: ", NULL),
{.cb = _chain_no_errors_cb, .data = NULL},
eina_future_cb_console("Expecting number 4. Got: ", NULL),
{.cb = _chain_no_errors_cb, .data = NULL},
eina_future_cb_console("Expecting number 8. Got: ", NULL),
{.cb = _chain_no_errors_cb, .data = NULL},
eina_future_cb_console("Expecting number 16. Got: ", NULL));
}
static Eina_Value
_chain_with_error_cb(void *data EINA_UNUSED, const Eina_Value v EINA_UNUSED, const Eina_Future *dead_future EINA_UNUSED)
{
Eina_Value err;
eina_value_setup(&err, EINA_VALUE_TYPE_ERROR);
eina_value_set(&err, E2BIG);
return err;
}
static void
_chain_with_error(void)
{
eina_future_chain(_int_future_get(),
{ _chain_with_error_cb, NULL },
eina_future_cb_console("Expecting argument list too long. Got: ", NULL),
{ .cb = _simple_err, .data = NULL });
}
static Eina_Value
_delayed_resolve(void *data, const Eina_Value v, const Eina_Future *dead_future EINA_UNUSED)
{
Inner_Promise_Ctx *ctx = data;
Eina_Value new_v;
eina_value_setup(&new_v, EINA_VALUE_TYPE_STRING);
eina_value_set(&new_v, "Hello from inner future");
eina_promise_resolve(ctx->promise, new_v);
free(ctx);
return v;
}
static Eina_Value
_delayed_reject(void *data, const Eina_Value v, const Eina_Future *dead_future EINA_UNUSED)
{
Inner_Promise_Ctx *ctx = data;
eina_promise_reject(ctx->promise, ENETDOWN);
free(ctx);
return v;
}
static void
_inner_promise_cancel(void *data, const Eina_Promise *dead EINA_UNUSED)
{
Inner_Promise_Ctx *ctx = data;
eina_future_cancel(ctx->future);
free(ctx);
}
static Eina_Value
_chain_inner_cb(void *data, const Eina_Value v EINA_UNUSED, const Eina_Future *dead_future EINA_UNUSED)
{
Inner_Promise_Ctx *ctx;
Eina_Value r;
ctx = calloc(1, sizeof(Inner_Promise_Ctx));
EINA_SAFETY_ON_NULL_GOTO(ctx, err);
ctx->promise = eina_promise_new(_future_scheduler_get(), _inner_promise_cancel, ctx);
EINA_SAFETY_ON_NULL_GOTO(ctx->promise, err);
printf("Creating a new promise inside the future cb\n");
ctx->future = eina_future_then(_int_future_get(),
!data ? _delayed_resolve : _delayed_reject,
ctx);
return eina_promise_as_value(ctx->promise);
err:
eina_value_setup(&r, EINA_VALUE_TYPE_ERROR);
eina_value_set(&r, ENOMEM);
free(ctx);
return r;
}
static Eina_Value
_chain_inner_last_cb(void *data EINA_UNUSED, const Eina_Value v, const Eina_Future *dead_future EINA_UNUSED)
{
VALUE_TYPE_CHECK(v, EINA_VALUE_TYPE_STRING);
return v;
}
static void
_chain_inner_no_errors(void)
{
eina_future_chain(_int_future_get(),
{ .cb = _chain_inner_cb, .data = NULL },
eina_future_cb_console("Expecting message: 'Hello from inner future'. Got: ", NULL),
{ .cb = _chain_inner_last_cb, .data = NULL });
}
static Eina_Value
_err_inner_chain(void *data EINA_UNUSED, const Eina_Value v, const Eina_Future *dead_future EINA_UNUSED)
{
VALUE_TYPE_CHECK(v, EINA_VALUE_TYPE_ERROR);
ecore_main_loop_quit();
return v;
}
static void
_chain_inner_errors(void)
{
eina_future_chain(_int_future_get(),
{ .cb = _chain_inner_cb, .data = (void *)1 },
eina_future_cb_console("Expection network down error. Got: ", NULL),
{ .cb = _err_inner_chain, .data = NULL });
}
static Eina_Value
_canceled_cb(void *data EINA_UNUSED, const Eina_Value v, const Eina_Future *dead_future EINA_UNUSED)
{
VALUE_TYPE_CHECK(v, EINA_VALUE_TYPE_ERROR);
return v;
}
static void
_future_cancel(void)
{
Eina_Future *f;
f = eina_future_chain(_int_future_get(),
eina_future_cb_console("Expecting cancelled operation error. Got: ", NULL),
{ .cb = _canceled_cb, .data = NULL },
eina_future_cb_console("Expecting cancelled operation error. Got: ", NULL),
{ .cb = _canceled_cb, .data = NULL },
eina_future_cb_console("Expecting cancelled operation error. Got: ", NULL),
{ .cb = _canceled_cb, .data = NULL },
eina_future_cb_console("Expecting cancelled operation error. Got: ", NULL),
{ .cb = _canceled_cb, .data = NULL },
eina_future_cb_console("Expecting cancelled operation error. Got: ", NULL),
{ .cb = _canceled_cb, .data = NULL },
eina_future_cb_console("Expecting cancelled operation error. Got: ", NULL));
eina_future_cancel(f);
}
int
main(int argc EINA_UNUSED, char *argv[] EINA_UNUSED)
{
if (!eina_init())
{
fprintf(stderr, "Could not init eina\n");
return EXIT_FAILURE;
}
if (!ecore_init())
{
fprintf(stderr, "Could not init ecore\n");
goto err_ecore;
}
_simple();
_alternate_error();
_chain_no_errors();
_chain_with_error();
_chain_inner_no_errors();
_chain_inner_errors();
_future_cancel();
ecore_main_loop_begin();
eina_shutdown();
ecore_shutdown();
return EXIT_SUCCESS;
err_ecore:
eina_shutdown();
return EXIT_FAILURE;
}

View File

@ -251,6 +251,7 @@ ecore_init(void)
if (getenv("ECORE_FPS_DEBUG")) _ecore_fps_debug = 1;
if (_ecore_fps_debug) _ecore_fps_debug_init();
if (!ecore_mempool_init()) goto shutdown_mempool;
if (!_ecore_event_init()) goto shutdown_mempool;
_ecore_main_loop_init();
vpath = efl_add(EFL_VPATH_CORE_CLASS, NULL);

View File

@ -44,6 +44,7 @@ GENERIC_ALLOC_FREE(Ecore_Event, ecore_event);
//GENERIC_ALLOC_FREE(Ecore_Poller, ecore_poller);
GENERIC_ALLOC_FREE(Ecore_Pipe, ecore_pipe);
GENERIC_ALLOC_FREE(Ecore_Fd_Handler, ecore_fd_handler);
GENERIC_ALLOC_FREE(Efl_Loop_Promise_Simple_Data, efl_loop_promise_simple_data);
#ifdef _WIN32
GENERIC_ALLOC_FREE(Ecore_Win32_Handler, ecore_win32_handler);
#endif
@ -61,6 +62,7 @@ static Ecore_Mempool *mempool_array[] = {
// &ecore_poller_mp,
&ecore_pipe_mp,
&ecore_fd_handler_mp,
&efl_loop_promise_simple_data_mp,
#ifdef _WIN32
&ecore_win32_handler_mp
#endif
@ -87,6 +89,7 @@ ecore_mempool_init(void)
// MP_SIZE_INIT(Ecore_Poller, ecore_poller);
MP_SIZE_INIT(Ecore_Pipe, ecore_pipe);
MP_SIZE_INIT(Ecore_Fd_Handler, ecore_fd_handler);
MP_SIZE_INIT(Efl_Loop_Promise_Simple_Data, efl_loop_promise_simple_data);
#ifdef _WIN32
MP_SIZE_INIT(Ecore_Win32_Handler, ecore_win32_handler);
#endif

View File

@ -48,6 +48,15 @@ struct _Ecore_Event
};
GENERIC_ALLOC_SIZE_DECLARE(Ecore_Event);
typedef struct _Ecore_Future_Schedule_Entry
{
Eina_Future_Schedule_Entry base;
Eina_Future_Scheduler_Cb cb;
Eina_Future *future;
Ecore_Event *event;
Eina_Value value;
} Ecore_Future_Schedule_Entry;
static int events_num = 0;
static Ecore_Event *events = NULL;
static Ecore_Event *event_current = NULL;
@ -68,6 +77,10 @@ static int event_filters_delete_me = 0;
static int event_id_max = ECORE_EVENT_COUNT;
static int ecore_raw_event_type = ECORE_EVENT_NONE;
static void *ecore_raw_event_event = NULL;
static Ecore_Event_Handler *future_handler = NULL;
static int ECORE_EV_FUTURE_ID = -1;
static Eina_Mempool *mp_future_schedule_entry = NULL;
static Eina_Bool shutting_down = EINA_FALSE;
static void _ecore_event_purge_deleted(void);
static void *_ecore_event_del(Ecore_Event *event);
@ -268,6 +281,100 @@ _ecore_event_handler_del(Ecore_Event_Handler *event_handler)
return event_handler->data;
}
static Eina_Bool
ecore_future_dispatched(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
Ecore_Future_Schedule_Entry *entry = event;
entry->event = NULL;
entry->cb(entry->future, entry->value);
return EINA_FALSE;
}
static void
ecore_future_free(void *user_data, void *func_data EINA_UNUSED)
{
Ecore_Future_Schedule_Entry *entry = user_data;
/*
In case entry->event is not NULL, it means
that ecore is shutting down. In this case,
we must cancel the future otherwise Eina may
try to use it and lead to crashes.
*/
if (entry->event)
{
eina_future_cancel(entry->future);
eina_value_flush(&entry->value);
}
eina_mempool_free(mp_future_schedule_entry, entry);
}
static Eina_Future_Schedule_Entry *
ecore_future_schedule(Eina_Future_Scheduler *sched, Eina_Future_Scheduler_Cb cb, Eina_Future *future, Eina_Value value)
{
Ecore_Future_Schedule_Entry *entry = eina_mempool_malloc(mp_future_schedule_entry,
sizeof(Ecore_Future_Schedule_Entry));
EINA_SAFETY_ON_NULL_RETURN_VAL(entry, NULL);
entry->base.scheduler = sched;
entry->cb = cb;
entry->future = future;
entry->value = value;
entry->event = ecore_event_add(ECORE_EV_FUTURE_ID, entry, ecore_future_free, entry);
EINA_SAFETY_ON_NULL_GOTO(entry->event, err);
return &entry->base;
err:
eina_mempool_free(mp_future_schedule_entry, entry);
return NULL;
}
static void
ecore_future_recall(Eina_Future_Schedule_Entry *s_entry)
{
if (shutting_down) return;
Ecore_Future_Schedule_Entry *entry = (Ecore_Future_Schedule_Entry *)s_entry;
EINA_SAFETY_ON_NULL_RETURN(entry->event);
ecore_event_del(entry->event);
eina_value_flush(&entry->value);
entry->event = NULL;
}
static Eina_Future_Scheduler ecore_future_scheduler = {
.schedule = ecore_future_schedule,
.recall = ecore_future_recall,
};
Eina_Future_Scheduler *
_ecore_event_future_scheduler_get(void)
{
return &ecore_future_scheduler;
}
Eina_Bool
_ecore_event_init(void)
{
const char *choice = getenv("EINA_MEMPOOL");
if ((!choice) || (!choice[0])) choice = "chained_mempool";
shutting_down = EINA_FALSE;
ECORE_EV_FUTURE_ID = ecore_event_type_new();
future_handler = ecore_event_handler_add(ECORE_EV_FUTURE_ID, ecore_future_dispatched, NULL);
EINA_SAFETY_ON_NULL_GOTO(future_handler, err_handler);
//FIXME: Is 512 too high?
mp_future_schedule_entry = eina_mempool_add(choice, "Ecore_Future_Event",
NULL, sizeof(Ecore_Future_Schedule_Entry),
512);
EINA_SAFETY_ON_NULL_GOTO(mp_future_schedule_entry, err_pool);
return EINA_TRUE;
err_pool:
ecore_event_handler_del(future_handler);
future_handler = NULL;
err_handler:
ECORE_EV_FUTURE_ID = -1;
return EINA_FALSE;
}
void
_ecore_event_shutdown(void)
{
@ -275,6 +382,9 @@ _ecore_event_shutdown(void)
Ecore_Event_Handler *eh;
Ecore_Event_Filter *ef;
shutting_down = EINA_TRUE;
ecore_event_handler_del(future_handler);
future_handler = NULL;
while (events) _ecore_event_del(events);
event_current = NULL;
for (i = 0; i < event_handlers_num; i++)
@ -301,6 +411,7 @@ _ecore_event_shutdown(void)
event_filters_delete_me = 0;
event_filter_current = NULL;
event_filter_event_current = NULL;
ECORE_EV_FUTURE_ID = -1;
}
int

View File

@ -233,6 +233,15 @@ struct _Ecore_Fd_Handler
};
GENERIC_ALLOC_SIZE_DECLARE(Ecore_Fd_Handler);
typedef struct _Efl_Loop_Promise_Simple_Data {
union {
Ecore_Timer *timer;
Ecore_Idler *idler;
};
Eina_Promise *promise;
} Efl_Loop_Promise_Simple_Data;
GENERIC_ALLOC_SIZE_DECLARE(Efl_Loop_Promise_Simple_Data);
#ifdef _WIN32
struct _Ecore_Win32_Handler
{
@ -3112,6 +3121,277 @@ EFL_CALLBACKS_ARRAY_DEFINE(timeout,
{ EFL_LOOP_TIMER_EVENT_TICK, _efl_loop_timeout_cb },
{ EFL_EVENT_DEL, _efl_loop_timeout_force_cancel_cb });
static Eina_Future *
_efl_loop_Eina_FutureXXX_job(Eo *obj, Efl_Loop_Data *pd EINA_UNUSED)
{
// NOTE: Eolian should do efl_future_then() to bind future to object.
return efl_future_Eina_FutureXXX_then(obj,
eina_future_resolved(efl_loop_future_scheduler_get(obj),
EINA_VALUE_EMPTY));
}
static void
_efl_loop_Eina_FutureXXX_idle_cancel(void *data, const Eina_Promise *dead_ptr EINA_UNUSED)
{
Efl_Loop_Promise_Simple_Data *d = data;
ecore_idler_del(d->idler);
efl_loop_promise_simple_data_mp_free(d);
}
static Eina_Bool
_efl_loop_Eina_FutureXXX_idle_done(void *data)
{
Efl_Loop_Promise_Simple_Data *d = data;
eina_promise_resolve(d->promise, EINA_VALUE_EMPTY);
efl_loop_promise_simple_data_mp_free(d);
return EINA_FALSE;
}
static Eina_Future *
_efl_loop_Eina_FutureXXX_idle(Eo *obj, Efl_Loop_Data *pd EINA_UNUSED)
{
Efl_Loop_Promise_Simple_Data *d;
Eina_Promise *p;
d = efl_loop_promise_simple_data_calloc(1);
EINA_SAFETY_ON_NULL_RETURN_VAL(d, NULL);
d->idler = ecore_idler_add(_efl_loop_Eina_FutureXXX_idle_done, d);
EINA_SAFETY_ON_NULL_GOTO(d->idler, idler_error);
p = eina_promise_new(efl_loop_future_scheduler_get(obj),
_efl_loop_Eina_FutureXXX_idle_cancel, d);
// d is dead if p is NULL
EINA_SAFETY_ON_NULL_RETURN_VAL(p, NULL);
d->promise = p;
// NOTE: Eolian should do efl_future_then() to bind future to object.
return efl_future_Eina_FutureXXX_then(obj, eina_future_new(p));
idler_error:
efl_loop_promise_simple_data_mp_free(d);
return NULL;
}
static void
_efl_loop_Eina_FutureXXX_timeout_cancel(void *data, const Eina_Promise *dead_ptr EINA_UNUSED)
{
Efl_Loop_Promise_Simple_Data *d = data;
ecore_timer_del(d->timer);
efl_loop_promise_simple_data_mp_free(d);
}
static Eina_Bool
_efl_loop_Eina_FutureXXX_timeout_done(void *data)
{
Efl_Loop_Promise_Simple_Data *d = data;
eina_promise_resolve(d->promise, EINA_VALUE_EMPTY);
efl_loop_promise_simple_data_mp_free(d);
return EINA_FALSE;
}
static Eina_Future *
_efl_loop_Eina_FutureXXX_timeout(Eo *obj, Efl_Loop_Data *pd EINA_UNUSED, double time)
{
Efl_Loop_Promise_Simple_Data *d;
Eina_Promise *p;
d = efl_loop_promise_simple_data_calloc(1);
EINA_SAFETY_ON_NULL_RETURN_VAL(d, NULL);
d->timer = ecore_timer_add(time, _efl_loop_Eina_FutureXXX_timeout_done, d);
EINA_SAFETY_ON_NULL_GOTO(d->timer, timer_error);
p = eina_promise_new(efl_loop_future_scheduler_get(obj),
_efl_loop_Eina_FutureXXX_timeout_cancel, d);
// d is dead if p is NULL
EINA_SAFETY_ON_NULL_RETURN_VAL(p, NULL);
d->promise = p;
// NOTE: Eolian should do efl_future_then() to bind future to object.
return efl_future_Eina_FutureXXX_then(obj, eina_future_new(p));
timer_error:
efl_loop_promise_simple_data_mp_free(d);
return NULL;
}
typedef struct _Efl_Loop_Coro {
Eina_Promise *promise;
Eina_Coro *coro;
Efl_Loop *loop;
Eina_Future *scheduled;
Efl_Loop_Coro_Cb func;
const void *func_data;
Eina_Free_Cb func_free_cb;
Efl_Loop_Coro_Prio prio;
Eina_Value value;
} Efl_Loop_Coro;
static void
_efl_loop_coro_free(Efl_Loop_Coro *lc)
{
if (lc->func_free_cb) lc->func_free_cb((void *)lc->func_data);
if (lc->scheduled) eina_future_cancel(lc->scheduled);
eina_value_flush(&lc->value);
efl_unref(lc->loop);
free(lc);
}
static void _efl_loop_coro_reschedule(Efl_Loop_Coro *lc);
static Eina_Value
_efl_loop_coro_schedule_resolved(void *data, const Eina_Value value, const Eina_Future *dead_future EINA_UNUSED)
{
Efl_Loop_Coro *lc = data;
Eina_Future *awaiting = NULL;
if (value.type == EINA_VALUE_TYPE_ERROR)
{
Eina_Error err;
eina_value_get(&value, &err);
ERR("coro %p scheduled got error %s, try again.",
lc, eina_error_msg_get(err));
}
else if (!eina_coro_run(&lc->coro, NULL, &awaiting))
{
INF("coroutine %p finished with value type=%p (%s)",
lc, lc->value.type,
lc->value.type ? lc->value.type->name : "EMPTY");
eina_promise_resolve(lc->promise, lc->value);
lc->value = EINA_VALUE_EMPTY; // owned by promise
_efl_loop_coro_free(lc);
return value;
}
else if (awaiting)
{
DBG("coroutine %p is awaiting for future %p, do not reschedule", lc, awaiting);
eina_future_chain(awaiting,
{
.cb = _efl_loop_coro_schedule_resolved,
.data = lc,
.storage = &lc->scheduled,
},
efl_future_cb(lc->loop));
}
else _efl_loop_coro_reschedule(lc);
return value;
}
static void
_efl_loop_coro_reschedule(Efl_Loop_Coro *lc)
{
Eina_Future *f;
// high uses 0-timeout instead of job, since job
// is implemented using events and the Ecore implementation
// will never run timers or anything else, just the new jobs :-/
//
// TODO: bug report ecore_main loop bug.
if (lc->prio == EFL_LOOP_CORO_PRIO_HIGH)
f = efl_loop_Eina_FutureXXX_timeout(lc->loop, 0);
else
f = efl_loop_Eina_FutureXXX_idle(lc->loop);
DBG("coroutine %p rescheduled as future=%p", lc, f);
// NOTE: efl_future_cb() doesn't allow for extra 'data', so it matches
// methods more easily. However we need 'lc' and we can't store in
// loop since we'd not know the key for efl_key_data_get().
// Easy solution: use 2 futures, one to bind and another to resolve.
eina_future_chain(f,
{
.cb = _efl_loop_coro_schedule_resolved,
.data = lc,
.storage = &lc->scheduled,
},
efl_future_cb(lc->loop));
}
static void
_efl_loop_coro_cancel(void *data, const Eina_Promise *dead_promise EINA_UNUSED)
{
Efl_Loop_Coro *lc = data;
INF("canceled coroutine %p (coro=%p)", lc, lc->coro);
eina_coro_cancel(&lc->coro);
_efl_loop_coro_free(lc);
}
static const void *
_efl_loop_coro_cb(void *data, Eina_Bool canceled, Eina_Coro *coro)
{
Efl_Loop_Coro *lc = data;
if (canceled) lc->value = eina_value_error_init(ECANCELED);
else lc->value = lc->func((void *)lc->func_data, coro, lc->loop);
return lc;
}
static Eina_Future *
_efl_loop_coro(Eo *obj, Efl_Loop_Data *pd EINA_UNUSED, Efl_Loop_Coro_Prio prio, void *func_data, Efl_Loop_Coro_Cb func, Eina_Free_Cb func_free_cb)
{
Efl_Loop_Coro *lc;
Eina_Promise *p;
Eina_Future *f;
EINA_SAFETY_ON_NULL_RETURN_VAL(func, NULL);
lc = calloc(1, sizeof(Efl_Loop_Coro));
EINA_SAFETY_ON_NULL_GOTO(lc, calloc_failed);
lc->loop = efl_ref(obj);
lc->func = func;
lc->func_data = func_data;
lc->func_free_cb = func_free_cb;
lc->prio = prio;
lc->coro = eina_coro_new(_efl_loop_coro_cb, lc, EINA_CORO_STACK_SIZE_DEFAULT);
EINA_SAFETY_ON_NULL_GOTO(lc, coro_failed);
p = eina_promise_new(efl_loop_future_scheduler_get(obj),
_efl_loop_coro_cancel, lc);
// lc is dead if p is NULL
EINA_SAFETY_ON_NULL_GOTO(p, promise_failed);
lc->promise = p;
// must be done prior to reschedule, as it may resolve on errors
// and promises without futures are simply ignored, will remain
// alive.
f = eina_future_new(p);
_efl_loop_coro_reschedule(lc);
INF("new coroutine %p (coro=%p)", lc, lc->coro);
// NOTE: Eolian should do efl_future_then() to bind future to object.
return efl_future_Eina_FutureXXX_then(obj, f);
promise_failed:
// _efl_loop_coro_cancel() was called, func was run... just return.
// NOTE: Eolian should do efl_future_then() to bind future to object.
return efl_future_Eina_FutureXXX_then(obj,
eina_future_resolved(efl_loop_future_scheduler_get(obj),
eina_value_error_init(ENOMEM)));
coro_failed:
_efl_loop_coro_free(lc);
calloc_failed:
if (func_free_cb) func_free_cb((void *)func_data);
// NOTE: Eolian should do efl_future_then() to bind future to object.
return efl_future_Eina_FutureXXX_then(obj,
eina_future_resolved(efl_loop_future_scheduler_get(obj),
eina_value_error_init(ENOMEM)));
}
/* This event will be triggered when the main loop is destroyed and destroy its timers along */
static void _efl_loop_internal_cancel(Efl_Internal_Promise *p);
@ -3286,5 +3566,12 @@ _efl_loop_efl_version_get(Eo *obj EINA_UNUSED, Efl_Loop_Data *pd EINA_UNUSED)
return &version;
}
EOLIAN static Eina_Future_Scheduler *
_efl_loop_future_scheduler_get(Eo *obj EINA_UNUSED,
Efl_Loop_Data *pd EINA_UNUSED)
{
return _ecore_event_future_scheduler_get();
}
#include "efl_loop.eo.c"

View File

@ -81,6 +81,8 @@ extern int _ecore_log_dom;
typedef struct _Ecore_Factorized_Idle Ecore_Factorized_Idle;
typedef struct _Efl_Loop_Promise_Simple_Data Efl_Loop_Promise_Simple_Data;
typedef struct _Efl_Loop_Data Efl_Loop_Data;
struct _Efl_Loop_Data
{
@ -187,6 +189,10 @@ void _ecore_idle_enterer_call(Eo *loop);
void _ecore_idle_exiter_call(Eo *loop);
Eina_Future_Scheduler *_ecore_event_future_scheduler_get(void);
Eina_Bool _ecore_event_init(void);
void _ecore_event_shutdown(void);
int _ecore_event_exist(void);
Ecore_Event *_ecore_event_add(int type,
@ -367,6 +373,7 @@ GENERIC_ALLOC_FREE_HEADER(Ecore_Event, ecore_event);
//GENERIC_ALLOC_FREE_HEADER(Ecore_Poller, ecore_poller);
GENERIC_ALLOC_FREE_HEADER(Ecore_Pipe, ecore_pipe);
GENERIC_ALLOC_FREE_HEADER(Ecore_Fd_Handler, ecore_fd_handler);
GENERIC_ALLOC_FREE_HEADER(Efl_Loop_Promise_Simple_Data, efl_loop_promise_simple_data);
#ifdef _WIN32
GENERIC_ALLOC_FREE_HEADER(Ecore_Win32_Handler, ecore_win32_handler);
#endif
@ -382,6 +389,9 @@ void ecore_loop_promise_register(Efl_Loop *l, Efl_Promise *p);
void ecore_loop_promise_unregister(Efl_Loop *l, Efl_Promise *p);
void ecore_loop_promise_fulfill(Efl_Promise *p);
Eina_Bool efl_promise2_init(void);
void efl_promise2_shutdown(void);
// access to direct input cb
#define ECORE_EVAS_INTERNAL

View File

@ -1,4 +1,5 @@
import efl_types;
import eina_types;
struct Efl.Loop.Arguments {
[[EFL loop arguments data structure]]
@ -6,6 +7,41 @@ struct Efl.Loop.Arguments {
initialization: bool; [[Set to $true when the program should initialize its internal state. This happen once per process instance.]]
}
enum Efl.Loop.Coro.Prio {
[[Priority class for the coroutine.]]
high = 0, [[high priority coroutine, scheduled using zero-timers (will expire as soon as possible).]]
idle, [[low priority coroutine, scheduled when nothing else should run]]
}
function Efl.Loop.Coro.Cb {
params {
coro: ptr(Eina.Coro); [[The coroutine handle, used to $eina_coro_yield() and voluntarily give back control to the main loop until it's rescheduled.]]
loop: Efl.Loop; [[The loop that schedules this coroutine.]]
}
return: generic_value; [[Value that will resolve the promise,
being delivered to the future chain
attached to the coroutine. Note that the
value will be owned by the Efl_Loop_Coro
and Eina_Future subsystems and will be
flushed (eina_value_flush()) once
unused. Its contents must survive the
function return, that is, it shouldn't
keep pointers to the stack.
]]
}; [[Coroutine function, it will be called back from the
coroutine environment and when executed it's guaranteed that the
main loop will be paused, so shared resources are safe to access
(no locks are required).
\@note Eina_Coro may use threads, then take care to handle
thread-local-storage (TLS) details properly, eventually you
may consider eina_coro_hook_add() to be informed when the
main or coroutine will exit and enter. For instance this is
used by Efl_Object (handled transparently for the user).
]]
class Efl.Loop (Efl.Object)
{
[[The Efl Main Loop
@ -69,6 +105,72 @@ class Efl.Loop (Efl.Object)
@in exit_code: ubyte; [[Returned value by begin()]]
}
}
@property future_scheduler {
[[Gets the Eina_Future_Scheduler for a given mainloop.
The Eina_Future_Scheduler returned by this function
should be used for creating promises (eina_promise_new())
so then can properly schedule resolve/reject events.
]]
get {}
values {
scheduler: ptr(Eina.Future.Scheduler); [[The scheduler.]]
}
}
Eina_FutureXXX_job {
[[A future promise that will be resolved from a clean main
loop context as soon as possible.
This has higher priority, for low priority use
@.Eina_FutureXXX_idle
]]
return: own(ptr(Eina.Future)) /* TODO: future<void> */; [[The future handle.]]
}
Eina_FutureXXX_idle {
[[A future promise that will be resolved from a clean main
loop context as soon as the main loop is idle.
This is a low priority version of @.Eina_FutureXXX_job
]]
return: own(ptr(Eina.Future)) /* TODO: future<void> */; [[The future handle.]]
}
Eina_FutureXXX_timeout {
[[A future promise that will be resolved from a clean main
loop context after $time seconds.]]
params {
@in time: double; [[The time from now in second that the main loop will wait before triggering it.]]
}
return: own(ptr(Eina.Future)) /* future<void> */; [[The future handle.]]
}
coro {
[[A future promise that will be resolved using a coroutine.
A coroutine is a function that will be executed
cooperatively with the main loop. The main loop will
schedule the coroutine, explicitly giving control to it --
by then the main loop is paused. The coroutine must then
finish and return, or yield control back to the main loop
using $eina_coro_yield(). This allows for shared context
to be safely interchanged with the main loop -- it is
guaranteed that if the coroutine is running, the main loop
is pause; if the main loop is running the coroutine is
paused.
Coroutines are implemented with @Eina.Coro, see their API
and how it's exposed in your language -- it may be the
case that you don't need to worry and it will be managed
transparently by your language/binding.
Once finished the coroutine returns a value, that will be
used to resolve the promise, propagating thru the future
chain.
]]
params {
@in priority: Efl.Loop.Coro.Prio; [[The priority used to schedule the coroutine.]]
@in func: Efl.Loop.Coro.Cb @nonull; [[The function to run as a coroutine.]]
}
return: own(ptr(Eina.Future)) /* future<> */; [[The future handle, it provides the value returned by $func once it exits.]]
}
job {
[[Will execute that promise in the near future.]]
params {

View File

@ -273,6 +273,8 @@ extern "C" {
#include <eina_freeq.h>
#include <eina_slstr.h>
#include <eina_debug.h>
#include <eina_promise.h>
#include <eina_coro.h>
#undef EAPI
#define EAPI

642
src/lib/eina/eina_coro.c Normal file
View File

@ -0,0 +1,642 @@
/* EINA - EFL data type library
* Copyright (C) 2017 ProFUSION embedded systems
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library;
* if not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdlib.h>
#include <limits.h>
#include "eina_config.h"
#include "eina_private.h"
#include "eina_log.h"
#include "eina_mempool.h"
#include "eina_lock.h"
#include "eina_thread.h"
#include "eina_inarray.h"
#include "eina_promise.h"
/* undefs EINA_ARG_NONULL() so NULL checks are not compiled out! */
#include "eina_safety_checks.h"
#include "eina_coro.h"
#include "eina_value.h"
#include "eina_value_util.h"
static Eina_Mempool *_eina_coro_mp = NULL;
static Eina_Lock _eina_coro_lock;
static int _eina_coro_log_dom = -1;
static int _eina_coro_usage = 0;
static int _eina_coro_hooks_walking = 0;
static Eina_Inarray _eina_coro_hooks;
#ifdef CRIT
#undef CRIT
#endif
#define CRIT(...) EINA_LOG_DOM_CRIT(_eina_coro_log_dom, __VA_ARGS__)
#ifdef ERR
#undef ERR
#endif
#define ERR(...) EINA_LOG_DOM_ERR(_eina_coro_log_dom, __VA_ARGS__)
#ifdef INF
#undef INF
#endif
#define INF(...) EINA_LOG_DOM_INFO(_eina_coro_log_dom, __VA_ARGS__)
#ifdef DBG
#undef DBG
#endif
#define DBG(...) EINA_LOG_DOM_DBG(_eina_coro_log_dom, __VA_ARGS__)
typedef enum _Eina_Coro_Turn {
EINA_CORO_TURN_MAIN = 0,
EINA_CORO_TURN_COROUTINE
} Eina_Coro_Turn;
struct _Eina_Coro {
Eina_Coro_Cb func;
const void *data;
Eina_Future *awaiting;
Eina_Lock lock;
Eina_Condition condition;
Eina_Thread main;
Eina_Thread coroutine;
Eina_Bool finished;
Eina_Bool canceled;
Eina_Coro_Turn turn;
};
#define CORO_TURN_STR(turn) \
(((turn) == EINA_CORO_TURN_MAIN) ? "MAIN" : "COROUTINE")
#define CORO_FMT "coro=%p {func=%p data=%p turn=%s threads={%p%c %p%c} awaiting=%p}"
#define CORO_EXP(coro) \
coro, coro->func, coro->data, \
CORO_TURN_STR(coro->turn), \
(void *)coro->coroutine, \
eina_thread_self() == coro->coroutine ? '*' : 0, \
(void *)coro->main, \
eina_thread_self() == coro->main ? '*' : 0, \
coro->awaiting
#define EINA_CORO_CHECK(coro, turn, ...) \
do \
{ \
if ((!_eina_coro_mp) || (!eina_mempool_from(_eina_coro_mp, (coro)))) \
{ \
CRIT(#coro "=%p is invalid.", (coro)); \
return __VA_ARGS__; \
} \
else if ((turn == EINA_CORO_TURN_COROUTINE) && ((coro)->coroutine != eina_thread_self())) \
{ \
CRIT("must be called from coroutine! " CORO_FMT, CORO_EXP((coro))); \
return __VA_ARGS__; \
} \
else if ((turn == EINA_CORO_TURN_MAIN) && ((coro)->main != eina_thread_self())) \
{ \
CRIT("must be called from main thread! " CORO_FMT, CORO_EXP((coro))); \
return __VA_ARGS__; \
} \
} \
while (0)
#define EINA_CORO_CHECK_GOTO(coro, turn, label) \
do \
{ \
if ((!_eina_coro_mp) || (!eina_mempool_from(_eina_coro_mp, (coro)))) \
{ \
CRIT(#coro "=%p is invalid.", (coro)); \
goto label; \
} \
else if ((turn == EINA_CORO_TURN_COROUTINE) && ((coro)->coroutine != eina_thread_self())) \
{ \
CRIT("must be called from coroutine! " CORO_FMT, CORO_EXP((coro))); \
goto label; \
} \
else if ((turn == EINA_CORO_TURN_MAIN) && ((coro)->main != eina_thread_self())) \
{ \
CRIT("must be called from main thread! " CORO_FMT, CORO_EXP((coro))); \
goto label; \
} \
} \
while (0)
typedef struct _Eina_Coro_Hook {
Eina_Coro_Hook_Coro_Enter_Cb coro_enter;
Eina_Coro_Hook_Coro_Exit_Cb coro_exit;
Eina_Coro_Hook_Main_Enter_Cb main_enter;
Eina_Coro_Hook_Main_Exit_Cb main_exit;
const void *data;
} Eina_Coro_Hook;
EAPI Eina_Bool
eina_coro_hook_add(Eina_Coro_Hook_Coro_Enter_Cb coro_enter, Eina_Coro_Hook_Coro_Exit_Cb coro_exit, Eina_Coro_Hook_Main_Enter_Cb main_enter, Eina_Coro_Hook_Main_Exit_Cb main_exit, const void *data)
{
Eina_Coro_Hook hook = { coro_enter, coro_exit, main_enter, main_exit, data };
int idx;
eina_lock_take(&_eina_coro_lock);
EINA_SAFETY_ON_TRUE_GOTO(_eina_coro_hooks_walking > 0, error);
idx = eina_inarray_push(&_eina_coro_hooks, &hook);
EINA_SAFETY_ON_TRUE_GOTO(idx < 0, error);
eina_lock_release(&_eina_coro_lock);
return EINA_TRUE;
error:
eina_lock_release(&_eina_coro_lock);
return EINA_FALSE;
}
EAPI Eina_Bool
eina_coro_hook_del(Eina_Coro_Hook_Coro_Enter_Cb coro_enter, Eina_Coro_Hook_Coro_Exit_Cb coro_exit, Eina_Coro_Hook_Main_Enter_Cb main_enter, Eina_Coro_Hook_Main_Exit_Cb main_exit, const void *data)
{
Eina_Coro_Hook hook = { coro_enter, coro_exit, main_enter, main_exit, data };
int idx;
eina_lock_take(&_eina_coro_lock);
EINA_SAFETY_ON_TRUE_GOTO(_eina_coro_hooks_walking > 0, error);
idx = eina_inarray_remove(&_eina_coro_hooks, &hook);
EINA_SAFETY_ON_TRUE_GOTO(idx < 0, error);
eina_lock_release(&_eina_coro_lock);
return EINA_TRUE;
error:
eina_lock_release(&_eina_coro_lock);
return EINA_FALSE;
}
// opposite of the coro_exit, similar to coro_enter
static void
_eina_coro_hooks_main_exit(Eina_Coro *coro)
{
const Eina_Coro_Hook *itr;
eina_lock_take(&_eina_coro_lock);
_eina_coro_hooks_walking++;
eina_lock_release(&_eina_coro_lock);
EINA_INARRAY_FOREACH(&_eina_coro_hooks, itr)
{
if (!itr->main_exit) continue;
if (itr->main_exit((void *)itr->data, coro)) continue;
coro->canceled = EINA_TRUE;
ERR("failed hook exit=%p data=%p for main routine " CORO_FMT,
itr->main_exit, itr->data, CORO_EXP(coro));
}
}
// opposite of the coro_enter, similar to coro_exit
static void
_eina_coro_hooks_main_enter(Eina_Coro *coro)
{
const Eina_Coro_Hook *itr;
EINA_INARRAY_REVERSE_FOREACH(&_eina_coro_hooks, itr)
{
if (!itr->main_enter) continue;
itr->main_enter((void *)itr->data, coro);
}
eina_lock_take(&_eina_coro_lock);
_eina_coro_hooks_walking--;
eina_lock_release(&_eina_coro_lock);
}
static Eina_Bool
_eina_coro_hooks_coro_enter(Eina_Coro *coro)
{
const Eina_Coro_Hook *itr;
Eina_Bool r = EINA_TRUE;
eina_lock_take(&_eina_coro_lock);
_eina_coro_hooks_walking++;
eina_lock_release(&_eina_coro_lock);
EINA_INARRAY_FOREACH(&_eina_coro_hooks, itr)
{
if (!itr->coro_enter) continue;
if (itr->coro_enter((void *)itr->data, coro)) continue;
r = EINA_FALSE;
ERR("failed hook enter=%p data=%p for coroutine " CORO_FMT,
itr->coro_enter, itr->data, CORO_EXP(coro));
}
return r;
}
static void
_eina_coro_hooks_coro_exit(Eina_Coro *coro)
{
const Eina_Coro_Hook *itr;
EINA_INARRAY_REVERSE_FOREACH(&_eina_coro_hooks, itr)
{
if (!itr->coro_exit) continue;
itr->coro_exit((void *)itr->data, coro);
}
eina_lock_take(&_eina_coro_lock);
_eina_coro_hooks_walking--;
eina_lock_release(&_eina_coro_lock);
}
static void
_eina_coro_signal(Eina_Coro *coro, Eina_Coro_Turn turn)
{
DBG("signal turn=%s " CORO_FMT, CORO_TURN_STR(turn), CORO_EXP(coro));
eina_lock_take(&coro->lock);
coro->turn = turn;
eina_condition_signal(&coro->condition);
eina_lock_release(&coro->lock);
}
static void
_eina_coro_wait(Eina_Coro *coro, Eina_Coro_Turn turn)
{
DBG("waiting turn=%s " CORO_FMT, CORO_TURN_STR(turn), CORO_EXP(coro));
eina_lock_take(&coro->lock);
while (coro->turn != turn)
eina_condition_wait(&coro->condition);
eina_lock_release(&coro->lock);
eina_main_loop_define();
DBG("wait is over: turn=%s " CORO_FMT, CORO_TURN_STR(turn), CORO_EXP(coro));
}
static Eina_Bool
_eina_coro_hooks_coro_enter_and_get_canceled(Eina_Coro *coro)
{
if (!_eina_coro_hooks_coro_enter(coro)) return EINA_TRUE;
return coro->canceled;
}
static void *
_eina_coro_thread(void *data, Eina_Thread t EINA_UNUSED)
{
Eina_Coro *coro = data;
void *result = NULL;
Eina_Bool canceled = EINA_FALSE;
_eina_coro_wait(coro, EINA_CORO_TURN_COROUTINE);
canceled = _eina_coro_hooks_coro_enter_and_get_canceled(coro);
DBG("call (canceled=%hhu) " CORO_FMT, canceled, CORO_EXP(coro));
result = (void *)coro->func((void *)coro->data, canceled, coro);
DBG("finished with result=%p " CORO_FMT, result, CORO_EXP(coro));
_eina_coro_hooks_coro_exit(coro);
coro->finished = EINA_TRUE;
_eina_coro_signal(coro, EINA_CORO_TURN_MAIN);
return result;
}
static Eina_Coro *
_eina_coro_alloc(void)
{
Eina_Coro *coro = NULL;
eina_lock_take(&_eina_coro_lock);
if (EINA_UNLIKELY(!_eina_coro_mp))
{
const char *choice, *tmp;
#ifdef EINA_DEFAULT_MEMPOOL
choice = "pass_through";
#else
choice = "chained_mempool";
#endif
tmp = getenv("EINA_MEMPOOL");
if (tmp && tmp[0])
choice = tmp;
_eina_coro_mp = eina_mempool_add
(choice, "coro", NULL, sizeof(Eina_Coro), 16);
if (!_eina_coro_mp)
{
ERR("Mempool for coro cannot be allocated in coro init.");
goto end;
}
}
coro = eina_mempool_calloc(_eina_coro_mp, sizeof(Eina_Coro));
if (coro) _eina_coro_usage++;
end:
eina_lock_release(&_eina_coro_lock);
return coro;
}
static void
_eina_coro_free(Eina_Coro *coro)
{
EINA_SAFETY_ON_NULL_RETURN(coro);
eina_lock_take(&_eina_coro_lock);
eina_mempool_free(_eina_coro_mp, coro);
_eina_coro_usage--;
if (_eina_coro_usage == 0)
{
eina_mempool_del(_eina_coro_mp);
_eina_coro_mp = NULL;
}
eina_lock_release(&_eina_coro_lock);
}
EAPI Eina_Coro *
eina_coro_new(Eina_Coro_Cb func, const void *data, size_t stack_size)
{
Eina_Coro *coro;
Eina_Bool r;
EINA_SAFETY_ON_NULL_RETURN_VAL(func, NULL);
coro = _eina_coro_alloc();
EINA_SAFETY_ON_NULL_RETURN_VAL(coro, NULL);
coro->func = func;
coro->data = data;
r = eina_lock_new(&coro->lock);
EINA_SAFETY_ON_FALSE_GOTO(r, failed_lock);
r = eina_condition_new(&coro->condition, &coro->lock);
EINA_SAFETY_ON_FALSE_GOTO(r, failed_condition);
coro->main = eina_thread_self();
coro->coroutine = 0;
coro->finished = EINA_FALSE;
coro->canceled = EINA_FALSE;
coro->turn = EINA_CORO_TURN_MAIN;
/* eina_thread_create() doesn't take attributes so we can set stack size */
if (stack_size)
DBG("currently stack size is ignored! Using thread default.");
if (!eina_thread_create(&coro->coroutine,
EINA_THREAD_NORMAL, -1,
_eina_coro_thread, coro))
{
ERR("could not create thread for " CORO_FMT, CORO_EXP(coro));
goto failed_thread;
}
INF(CORO_FMT, CORO_EXP(coro));
return coro;
failed_thread:
eina_condition_free(&coro->condition);
failed_condition:
eina_lock_free(&coro->lock);
failed_lock:
_eina_coro_free(coro);
return NULL;
}
EAPI Eina_Bool
eina_coro_yield(Eina_Coro *coro)
{
EINA_CORO_CHECK(coro, EINA_CORO_TURN_COROUTINE, EINA_FALSE);
_eina_coro_hooks_coro_exit(coro);
_eina_coro_signal(coro, EINA_CORO_TURN_MAIN);
_eina_coro_wait(coro, EINA_CORO_TURN_COROUTINE);
return !_eina_coro_hooks_coro_enter_and_get_canceled(coro);
}
EAPI Eina_Bool
eina_coro_run(Eina_Coro **p_coro, void **p_result, Eina_Future **p_awaiting)
{
Eina_Coro *coro;
if (p_result) *p_result = NULL;
if (p_awaiting) *p_awaiting = NULL;
EINA_SAFETY_ON_NULL_RETURN_VAL(p_coro, EINA_FALSE);
EINA_CORO_CHECK(*p_coro, EINA_CORO_TURN_MAIN, EINA_FALSE);
coro = *p_coro;
_eina_coro_hooks_main_exit(coro);
_eina_coro_signal(coro, EINA_CORO_TURN_COROUTINE);
_eina_coro_wait(coro, EINA_CORO_TURN_MAIN);
_eina_coro_hooks_main_enter(coro);
if (EINA_UNLIKELY(coro->finished)) {
void *result;
DBG("coroutine finished, join thread " CORO_FMT, CORO_EXP(coro));
result = eina_thread_join(coro->coroutine);
INF("coroutine finished with result=%p " CORO_FMT,
result, CORO_EXP(coro));
if (p_result) *p_result = result;
if (coro->awaiting) eina_future_cancel(coro->awaiting);
eina_condition_free(&coro->condition);
eina_lock_free(&coro->lock);
_eina_coro_free(coro);
*p_coro = NULL;
return EINA_FALSE;
}
if (p_awaiting) *p_awaiting = coro->awaiting;
DBG("coroutine yielded, must run again " CORO_FMT, CORO_EXP(coro));
return EINA_TRUE;
}
typedef struct _Eina_Coro_Await_Data {
Eina_Coro *coro;
Eina_Value *p_value;
Eina_Bool resolved;
} Eina_Coro_Await_Data;
static Eina_Value
_eina_coro_await_cb(void *data, const Eina_Value value, const Eina_Future *dead_future)
{
Eina_Coro_Await_Data *d = data;
DBG("future %p resolved with value type %p (%s) " CORO_FMT,
dead_future, value.type, value.type ? value.type->name : "EMPTY",
CORO_EXP(d->coro));
if (d->p_value)
{
// copy is needed as value contents is flushed when this function returns.
if (!value.type) *d->p_value = value;
else if (!eina_value_copy(&value, d->p_value))
{
ERR("Value cannot be copied - unusable with Eina_Future: %p (%s)", value.type, value.type->name);
eina_value_setup(d->p_value, EINA_VALUE_TYPE_ERROR);
eina_value_set(d->p_value, ENOTSUP);
}
}
d->resolved = EINA_TRUE;
return value;
}
EAPI Eina_Bool
eina_coro_await(Eina_Coro *coro, Eina_Future *f, Eina_Value *p_value)
{
Eina_Coro_Await_Data data = { coro, p_value, EINA_FALSE };
if (p_value) *p_value = EINA_VALUE_EMPTY;
EINA_CORO_CHECK_GOTO(coro, EINA_CORO_TURN_COROUTINE, no_coro);
EINA_SAFETY_ON_TRUE_GOTO(coro->awaiting != NULL, no_coro);
// storage will be NULL once future dies...
f = eina_future_then(f, _eina_coro_await_cb, &data, &coro->awaiting);
if (!f) return EINA_FALSE;
INF("await future %p " CORO_FMT, f, CORO_EXP(coro));
while (eina_coro_yield(coro) && !data.resolved)
DBG("future %p still pending " CORO_FMT, f, CORO_EXP(coro));
if (!data.resolved)
{
INF("future %p still pending and coroutine was canceled " CORO_FMT,
f, CORO_EXP(coro));
if (p_value)
{
eina_value_flush(p_value);
*p_value = eina_value_error_init(ECANCELED);
}
return EINA_FALSE;
}
INF("future %p resolved! continue coroutine " CORO_FMT, f, CORO_EXP(coro));
return EINA_TRUE;
no_coro:
if (p_value) *p_value = eina_value_error_init(EINVAL);
eina_future_cancel(f);
return EINA_FALSE;
}
EAPI void *
eina_coro_cancel(Eina_Coro **p_coro)
{
void *result = NULL;
Eina_Coro *coro;
EINA_SAFETY_ON_NULL_RETURN_VAL(p_coro, NULL);
EINA_CORO_CHECK(*p_coro, EINA_CORO_TURN_MAIN, NULL);
coro = *p_coro;
coro->canceled = EINA_TRUE;
if (coro->awaiting) eina_future_cancel(coro->awaiting);
DBG("marked as canceled, run so it can exit... " CORO_FMT, CORO_EXP(coro));
while (eina_coro_run(p_coro, &result, NULL))
DBG("did not exited, try running again..." CORO_FMT, CORO_EXP(coro));
// be careful, coro is dead!
INF("coroutine %p canceled and returned %p", coro, result);
return result;
}
/**
* @internal
* @brief Initialize the coroutine module.
*
* @return #EINA_TRUE on success, #EINA_FALSE on failure.
*
* This function sets up the coroutine module of Eina. It is called by
* eina_init().
*
* This function creates mempool to speed up and keep safety of coro
* handles, using EINA_MEMPOOL environment variable if it is set to
* choose the memory pool type to use.
*
* @see eina_init()
*/
Eina_Bool
eina_coro_init(void)
{
_eina_coro_log_dom = eina_log_domain_register("eina_coro",
EINA_LOG_COLOR_DEFAULT);
if (_eina_coro_log_dom < 0)
{
EINA_LOG_ERR("Could not register log domain: eina_coro");
return EINA_FALSE;
}
eina_lock_new(&_eina_coro_lock);
_eina_coro_usage = 0;
_eina_coro_hooks_walking = 0;
eina_inarray_step_set(&_eina_coro_hooks, sizeof(_eina_coro_hooks),
sizeof(Eina_Coro_Hook), 1);
return EINA_TRUE;
}
/**
* @internal
* @brief Shut down the coroutine module.
*
* @return #EINA_TRUE on success, #EINA_FALSE on failure.
*
* This function shuts down the coroutine module set up by
* eina_coro_init(). It is called by eina_shutdown().
*
* @see eina_shutdown()
*/
Eina_Bool
eina_coro_shutdown(void)
{
eina_lock_take(&_eina_coro_lock);
EINA_SAFETY_ON_TRUE_GOTO(_eina_coro_usage > 0, in_use);
eina_lock_release(&_eina_coro_lock);
eina_lock_free(&_eina_coro_lock);
eina_inarray_flush(&_eina_coro_hooks);
eina_log_domain_unregister(_eina_coro_log_dom);
_eina_coro_log_dom = -1;
return EINA_TRUE;
in_use:
eina_lock_release(&_eina_coro_lock);
return EINA_FALSE;
}

554
src/lib/eina/eina_coro.h Normal file
View File

@ -0,0 +1,554 @@
/* EINA - EFL data type library
* Copyright (C) 2017 ProFUSION embedded systems
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library;
* if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef EINA_CORO_H_
#define EINA_CORO_H_
#include "eina_config.h"
#include "eina_types.h"
#include "eina_error.h"
typedef struct _Eina_Future Eina_Future;
typedef struct _Eina_Value Eina_Value;
/**
* @addtogroup Eina_Tools_Group Tools
*
* @{
*/
/**
* @defgroup Eina_Coro_Group Co-routines
*
* Co-routines are cooperative threads, that is, their execution will
* stop the caller's thread, execute the coroutine until it finishes
* or yield, then give back control to the caller thread.
*
* The purpose of this primitive is to allow two functions to run with
* their own stack and registers and be sure that the caller thread
* won't run meanwhile, then shared context (variables, pointers) do
* not need locks as this is done implicitly by the design: if one
* thread is running you can be sure the other is not. The coroutine
* must explicitly give back control to the caller thread, either by
* eina_coro_yield() or by return.
*
* Multiple coroutines may exist at a given time, however if they are
* managed by the same caller then it's guaranteed that they will
* cooperate among themselves.
*
* @note The current implementation @b may use real threads with a
* lock and condition variable to ensure the behavior, this is
* an implementation detail that must not be relied upon.
* Depending on the platform it may use ucontext.h (SysV-like)
* or custom task save/restore. Libraries can use
* eina_coro_hook_add() to be called when the coroutine code
* will enter and exit, being able to retrieve context and set
* some other locks such or Efl_Object's efl_domain_data_adopt()
* (done automatically from efl_object_init()).
*
* @see @ref Eina_Thread_Group for regular concurrent threads.
*
* @since 1.21
* @{
*/
typedef struct _Eina_Coro Eina_Coro;
/**
* @typedef Eina_Coro_Hook_Coro_Enter_Cb
*
* @brief Type for the definition of a coroutine hook.
*
* The pointer will be called back with the given @c data and the
* coroutine that will be entered or exited.
*
* The coroutine "enters" when eina_coro_run() is called and "exits"
* when the provided function returns or calls eina_coro_yield().
*
* If the callback returns #EINA_FALSE, then eina_coro_yield() will
* return that value, meaning the coroutine should voluntarily exit.
*
* All hooks are called, and if any of them returns #EINA_FALSE,
* eina_coro_yield() will return the same.
*
* If #EINA_FALSE is returned prior the first execution of the
* coroutine, then the coroutine will get a canceled #EINA_TRUE as
* parameter. This allows coroutine to cleanup whatever is needed
* and return.
*
* @since 1.21
*/
typedef Eina_Bool (*Eina_Coro_Hook_Coro_Enter_Cb)(void *data, const Eina_Coro *coro);
/**
* @typedef Eina_Coro_Hook_Coro_Exit_Cb
*
* @brief Type for the definition of a coroutine exit hook.
*
* The pointer will be called back with the given @c data and the
* coroutine that exited.
*
* The coroutine "enters" when eina_coro_run() is called and "exits"
* when the provided function returns or calls eina_coro_yield().
*
* Exit hooks are always called in the reverse order they were added,
* that is, the last added hook will run first (stack).
*
* @since 1.21
*/
typedef void (*Eina_Coro_Hook_Coro_Exit_Cb)(void *data, const Eina_Coro *coro);
/**
* @typedef Eina_Coro_Hook_Main_Enter_Cb
*
* @brief Type for the definition of a main routine hook.
*
* The pointer will be called back with the given @c data and the
* coroutine that will be entered or exited.
*
* The coroutine "enters" when eina_coro_run() is called and "exits"
* when the provided function returns or calls eina_coro_yield().
*
* Unlike the coroutine exit hooks, the main routine Exit hooks are
* always called in the reverse order they were added, that is, the
* last added hook will run first (stack). This is because they match
* Eina_Coro_Hook_Coro_Exit_Cb, a coroutine exits so the main routine
* can enter.
*
* @since 1.21
*/
typedef void (*Eina_Coro_Hook_Main_Enter_Cb)(void *data, const Eina_Coro *coro);
/**
* @typedef Eina_Coro_Hook_Main_Exit_Cb
*
* @brief Type for the definition of a main routine exit hook.
*
* The pointer will be called back with the given @c data and the
* coroutine that exited.
*
* The coroutine "enters" when eina_coro_run() is called and "exits"
* when the provided function returns or calls eina_coro_yield().
*
* Unlike the coroutine enter hooks, the main routine Enter hooks are
* called in forward order, that is, the first added hook will run
* first. This is because they match Eina_Coro_Hook_Coro_Enter_Cb, a
* main routine exits so the coroutine can enter.
*
* If the callback returns #EINA_FALSE, then eina_coro_yield() will
* return that value, meaning the coroutine should voluntarily exit.
*
* All hooks are called, and if any of them returns #EINA_FALSE,
* eina_coro_yield() will return the same.
*
* If #EINA_FALSE is returned prior the first execution of the
* coroutine, then the coroutine will get a canceled #EINA_TRUE as
* parameter. This allows coroutine to cleanup whatever is needed
* and return.
*
* @since 1.21
*/
typedef Eina_Bool (*Eina_Coro_Hook_Main_Exit_Cb)(void *data, const Eina_Coro *coro);
/**
* Adds a hook to the coroutine subsystem.
*
* The coroutine "enters" when eina_coro_run() is called and "exits"
* when the provided function returns or calls eina_coro_yield().
*
* The main routine (the caller) is the opposite: when eina_coro_run()
* it will "exit" and it will "enter" before eina_coro_run() returns.
*
* Enter hooks are executed in order, while exit hooks are always
* called in the reverse order they were added, that is, the last
* added hook will run first (stack).
*
* If any enter hooks fail, then eina_coro_yield() will return
* #EINA_FALSE or the parameter @c canceled as #EINA_TRUE will be
* given to the coroutine function. This allows coroutines to cleanup
* and exit.
*
* The flow is the following:
*
* @li main_exit is called to notify main routine will be stopped.
* @li coro_enter is called to notify the coroutine will be started.
* @li coro_exit is called to notify the coroutine stopped.
* @li main_enter is called to notify main routine will be resumed.
*
* They may be useful to properly setup environment prior to callback
* user code, like Efl_Object must check if we're running in an actual
* thread and adopt Efl_Domain_Data so objects are accessible from the
* coroutine -- this is done automatically by efl_object_init().
*
* @note this must @b NOT be called from within the coroutine itself,
* usually do this from your "init".
*
* @param coro_enter the hook to be called when the coroutine
* enters. May be #NULL if no enter hook is needed.
* @param coro_exit the hook to be called when the coroutine
* exits. May be #NULL if no exit hook is needed.
* @param main_enter the hook to be called when the caller
* enters. May be #NULL if no enter hook is needed.
* @param main_exit the hook to be called when the caller
* exits. May be #NULL if no exit hook is needed.
* @param data the context to pass to hooks. May be #NULL if no
* context is needed.
* @return #EINA_TRUE on success, #EINA_FALSE on failure.
*
* @since 1.12
*/
EAPI Eina_Bool eina_coro_hook_add(Eina_Coro_Hook_Coro_Enter_Cb coro_enter, Eina_Coro_Hook_Coro_Exit_Cb coro_exit, Eina_Coro_Hook_Main_Enter_Cb main_enter, Eina_Coro_Hook_Main_Exit_Cb main_exit, const void *data);
/**
* Removes a hook from the coroutine subsystem.
*
* @note this must @b NOT be called from within the coroutine itself,
* usually do this from your "init".
*
* @param coro_enter the pointer that was given to eina_coro_hook_add().
* @param coro_exit the pointer that was given to eina_coro_hook_add().
* @param main_enter the pointer that was given to eina_coro_hook_add().
* @param main_exit the pointer that was given to eina_coro_hook_add().
* @param data the pointer that was given to eina_coro_hook_add().
* @return #EINA_TRUE on success, #EINA_FALSE on failure (ie: not found).
*
* @since 1.12
*/
EAPI Eina_Bool eina_coro_hook_del(Eina_Coro_Hook_Coro_Enter_Cb coro_enter, Eina_Coro_Hook_Coro_Exit_Cb coro_exit, Eina_Coro_Hook_Main_Enter_Cb main_enter, Eina_Coro_Hook_Main_Exit_Cb main_exit, const void *data);
/**
* @typedef Eina_Coro_Cb
* Type for the definition of a coroutine callback function.
*
* Once eina_coro_new() returns non-NULL, this function will be always
* called. However it may receive @c canceled as #EINA_TRUE, in this
* case it should cleanup and exit as soon as possible.
*
* @note The current implementation @b may use real threads with a
* lock and condition variable to ensure the behavior, this is
* an implementation detail that must not be relied upon.
* Depending on the platform it may use ucontext.h (SysV-like)
* or custom task save/restore. Libraries can use
* eina_coro_hook_add() to be called when the coroutine code
* will enter and exit, being able to retrieve context and set
* some other locks such as the Python's GIL or Efl_Object's
* efl_domain_data_adopt() (done automatically from
* efl_object_init()).
*
* @since 1.21
*/
typedef const void *(*Eina_Coro_Cb)(void *data, Eina_Bool canceled, Eina_Coro *coro);
/**
* @def EINA_CORO_STACK_SIZE_DEFAULT
*
* Use the system's default stack size, usually @c PTHREAD_STACK_MIN
* (16Kb - 16384 bytes).
*
* @since 1.21
*/
#define EINA_CORO_STACK_SIZE_DEFAULT 0
/**
* @brief Creates a new coroutine.
*
* Allocates a coroutine environment using the given @a stack_size to
* execute @a func with the given context @a data. The coroutine must
* be explicitly executed with eina_coro_run(), that will stop the
* caller and let the coroutine work.
*
* @note The coroutine is @b not executed when it's created, that is
* managed by eina_coro_run().
*
* @note Currently @a stack_size is ignored, the default thread stack
* size will be used.
*
* @param func function to run in the coroutine. Must @b not be @c NULL.
* @param data context data to provide to @a func as first argument.
* @param stack_size defines the stack size to use to run the function
* @a func. Usually must be multiple of @c PAGE_SIZE and most
* systems will define a minimum stack limit such as 16Kb -
* those nuances are handled automatically for you. Most users
* want #EINA_CORO_STACK_SIZE_DEFAULT.
* @return newly allocated coroutine handle on success, #NULL on failure.
*
* @see eina_coro_run()
*
* @since 1.21
*/
EAPI Eina_Coro *eina_coro_new(Eina_Coro_Cb func, const void *data, size_t stack_size) EINA_ARG_NONNULL(1) EINA_WARN_UNUSED_RESULT;
/**
* @brief Yields control from coroutine to caller.
*
* This @b must be called from within the @b coroutine and will pause it,
* giving back control to the caller of eina_coro_run(). The coroutine
* will remain stalled until eina_coro_run() is executed again.
*
* Yield can return #EINA_FALSE if any hooks fail, see
* eina_coro_hook_add(), or if the coroutine is canceled with
* eina_coro_cancel().
*
* @note The current implementation @b may use real threads with a
* lock and condition variable to ensure the behavior, this is
* an implementation detail that must not be relied upon.
* Depending on the platform it may use ucontext.h (SysV-like)
* or custom task save/restore. Libraries can use
* eina_coro_hook_add() to be called when the coroutine code
* will enter and exit, being able to retrieve context and set
* some other locks such as the Python's GIL or Efl_Object's
* efl_domain_data_adopt() (done automatically from
* efl_object_init()).
*
* @param coro the coroutine that will yield control. Must not be #NULL.
*
* @return #EINA_TRUE if coroutine is allowed to continue,
* #EINA_FALSE if the coroutine should cleanup and exit.
* Refer to macros eina_coro_yield_or_return() or
* eina_coro_yield_or_goto() to aid such tasks.
*
* @see eina_coro_run()
* @see eina_coro_new()
*
* @since 1.21
*/
EAPI Eina_Bool eina_coro_yield(Eina_Coro *coro) EINA_ARG_NONNULL(1) EINA_WARN_UNUSED_RESULT;
/**
* #def eina_coro_yield_or_return(coro, val)
*
* This will yield control back to the main thread and wait. If the
* yield returns #EINA_FALSE then it will return the given value.
*
* @param coro the coroutine handle. Must not be #NULL.
* @param val the value to return (void *) if yield returns #EINA_FALSE.
*
* @see eina_coro_yield()
* @see eina_coro_yield_or_goto()
*
* @since 1.21
*/
#define eina_coro_yield_or_return(coro, val) \
do { if (!eina_coro_yield(coro)) return (val); } while (0)
/**
* #def eina_coro_yield_or_goto(coro, label)
*
* This will yield control back to the main thread and wait. If the
* yield returns #EINA_FALSE then it will goto the given label.
*
* @param coro the coroutine handle. Must not be #NULL.
* @param label the label to jump if yield returns #EINA_FALSE.
*
* @see eina_coro_yield()
* @see eina_coro_yield_or_return()
*
* @since 1.21
*/
#define eina_coro_yield_or_goto(coro, label) \
do { if (!eina_coro_yield(coro)) goto label; } while (0)
/**
* @brief Yield control from coroutine to the caller and report it's
* awaiting a future to be resolved.
*
* This @b must be called from within the @b coroutine and will pause
* it, giving back control to the caller of eina_coro_run(). The
* coroutine will remain stalled until eina_coro_run() is executed
* again. The caller of eina_coro_run() will get @c p_awaiting set so
* it can wait for the future to be resolved and improve its
* scheduling. Note that this will busy wait using eina_coro_yield()
* until the future resolves, then it's safe to naively call
* eina_coro_run() if the future is still pending -- but it will keep
* CPU consumption to its maximum.
*
* Await can return #EINA_FALSE if any hooks fail, see
* eina_coro_hook_add(), or if the coroutine is canceled with
* eina_coro_cancel().
*
* @note The current implementation @b may use real threads with a
* lock and condition variable to ensure the behavior, this is
* an implementation detail that must not be relied upon.
* Depending on the platform it may use ucontext.h (SysV-like)
* or custom task save/restore. Libraries can use
* eina_coro_hook_add() to be called when the coroutine code
* will enter and exit, being able to retrieve context and set
* some other locks such as the Python's GIL or Efl_Object's
* efl_domain_data_adopt() (done automatically from
* efl_object_init()).
*
* @param coro the coroutine that will await control. Must not be #NULL.
* @param future the future that this will wait. Must not be #NULL.
* @param[out] p_value the pointer to value containing the future
* resolution. If given the value will be owned by caller and
* must be released with eina_value_flush(). May be #NULL.
*
* @return #EINA_TRUE if coroutine is allowed to continue,
* #EINA_FALSE if the coroutine should cleanup and exit.
* Refer to macros eina_coro_await_or_return() or
* eina_coro_await_or_goto() to aid such tasks.
*
* @see eina_coro_run()
* @see eina_coro_new()
* @see eina_coro_yield()
*
* @since 1.21
*/
EAPI Eina_Bool eina_coro_await(Eina_Coro *coro, Eina_Future *future, Eina_Value *p_value) EINA_ARG_NONNULL(1, 2) EINA_WARN_UNUSED_RESULT;
/**
* #def eina_coro_await_or_return(coro, future, p_value, val)
*
* This will yield control back to the main thread and wait for future
* to be resolved. If the await returns #EINA_FALSE then it will
* return the given value.
*
* @param coro the coroutine handle. Must not be #NULL.
* @param future the future that this will wait. Must not be #NULL.
* @param[out] p_value the pointer to value containing the future
* resolution. If given the value will be owned by caller and
* must be released with eina_value_flush(). May be #NULL.
* @param val the value to return (void *) if await returns #EINA_FALSE.
*
* @see eina_coro_await()
* @see eina_coro_await_or_goto()
*
* @since 1.21
*/
#define eina_coro_await_or_return(coro, future, p_value, val) \
do { if (!eina_coro_await(coro, future, p_value)) return (val); } while (0)
/**
* #def eina_coro_await_or_goto(coro, label)
*
* This will yield control back to the main thread and wait for future
* to be resolved. If the await returns #EINA_FALSE then it will goto
* the given label.
*
* @param coro the coroutine handle. Must not be #NULL.
* @param future the future that this will wait. Must not be #NULL.
* @param[out] p_value the pointer to value containing the future
* resolution. If given the value will be owned by caller and
* must be released with eina_value_flush(). May be #NULL.
* @param label the label to jump if await returns #EINA_FALSE.
*
* @see eina_coro_await()
* @see eina_coro_await_or_return()
*
* @since 1.21
*/
#define eina_coro_await_or_goto(coro, future, p_value, label) \
do { if (!eina_coro_await(coro, future, p_value)) goto label; } while (0)
/**
* @brief Run the coroutine and report if it's still alive.
*
* This @b must be called from @b outside the coroutine, what we call
* "the caller side", also known as "main thread". The caller will be
* paused and control will be handled to the coroutine until it
* eina_coro_yield() or exits (returns).
*
* If the coroutine exits (returns), then this function will free the
* coroutine created, make @a p_coro #NULL, set @a p_result to
* whatever the Eina_Coro_Cb returned and return #EINA_FALSE.
*
* If the coroutine yields, then this function will return #EINA_TRUE,
* meaning it must be called again.
*
* @note The current implementation @b may use real threads with a
* lock and condition variable to ensure the behavior, this is
* an implementation detail that must not be relied upon.
* Depending on the platform it may use ucontext.h (SysV-like)
* or custom task save/restore. Libraries can use
* eina_coro_hook_add() to be called when the coroutine code
* will enter and exit, being able to retrieve context and set
* some other locks such as the Python's GIL or Efl_Object's
* efl_domain_data_adopt() (done automatically from
* efl_object_init()).
*
* @param[inout] p_coro pointer to the coroutine that will be
* executed. Must @b not be #NULL.
* @param[out] p_result if the coroutine exited, then will be set to the
* returned value. May be #NULL.
* @param[out] p_awaiting if the coroutine is awaiting a future to be
* resolved, then the handle. The scheduler should consider
* eina_future_then() and only call the coroutine once it's
* resolved. This happens when coroutine used eina_coro_await()
* instead of eina_coro_yield().
*
* @return #EINA_TRUE if the coroutine yielded and the user must call
* eina_coro_run() again. #EINA_FALSE if the coroutine exited,
* then @a p_coro will point to #NULL and if @a p_result is
* given it will be set to whatever the coroutine @c func returned.
*
* @since 1.21
*/
EAPI Eina_Bool eina_coro_run(Eina_Coro **p_coro, void **p_result, Eina_Future **p_awaiting) EINA_ARG_NONNULL(1);
/**
* @brief Cancel the coroutine and wait for it to finish.
*
* This @b must be called from @b outside the coroutine, what we call
* "the caller side", also known as "main thread". The caller will be
* paused and control will be handled to the coroutine until it exits
* (returns).
*
* This function will free the coroutine created, make @a p_coro
* #NULL, set @a p_result to whatever the Eina_Coro_Cb returned and
* return #EINA_FALSE.
*
* @note This will busy wait on eina_coro_run() until the coroutine @b
* voluntarily finishes, it's not aborted in any way. The
* primitive eina_coro_yield() will return #EINA_FALSE when the
* coroutine is canceled, this gives the opportunity to cleanup
* and exit. Likewise, if canceled before it's ever ran, then
* it will pass @c canceled as #EINA_TRUE to the Eina_Coro_Cb
* given to eina_coro_new()
*
* @note The current implementation @b may use real threads with a
* lock and condition variable to ensure the behavior, this is
* an implementation detail that must not be relied upon.
* Depending on the platform it may use ucontext.h (SysV-like)
* or custom task save/restore. Libraries can use
* eina_coro_hook_add() to be called when the coroutine code
* will enter and exit, being able to retrieve context and set
* some other locks such as the Python's GIL or Efl_Object's
* efl_domain_data_adopt() (done automatically from
* efl_object_init()).
*
* @param[inout] p_coro pointer to the coroutine that will be
* executed. Must @b not be #NULL.
*
* @return The value returned by the coroutine function, same as would
* be returned in eina_coro_run()'s @c p_result parameter.
*
* @since 1.21
*/
EAPI void *eina_coro_cancel(Eina_Coro **p_coro) EINA_ARG_NONNULL(1);
/**
* @}
*/
/**
* @}
*/
#endif

View File

@ -74,6 +74,7 @@
#include "eina_evlog.h"
#include "eina_freeq.h"
#include "eina_slstr.h"
#include "eina_coro.h"
/*============================================================================*
* Local *
@ -159,6 +160,8 @@ EAPI Eina_Inlist *_eina_tracking = NULL;
S(file);
S(safepointer);
S(slstr);
S(promise);
S(coro);
#undef S
struct eina_desc_setup
@ -205,6 +208,8 @@ static const struct eina_desc_setup _eina_desc_setup[] = {
S(file),
S(safepointer),
S(slstr),
S(promise),
S(coro),
#undef S
};
static const size_t _eina_desc_setup_len = sizeof(_eina_desc_setup) /
@ -305,7 +310,6 @@ eina_init(void)
eina_cpu_count_internal();
eina_log_timing(_eina_log_dom, EINA_LOG_STATE_STOP, EINA_LOG_STATE_INIT);
_eina_main_count = 1;
eina_evlog("-eina_init", NULL, 0.0, NULL);
return 1;

1317
src/lib/eina/eina_promise.c Normal file

File diff suppressed because it is too large Load Diff

1489
src/lib/eina/eina_promise.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
#ifndef __EINA_PROMISE_PRIVATE_H__
#define __EINA_PROMISE_PRIVATE_H__
#define ERROR_DISPATCH(_cbs, _ret, _value, _data) \
do { \
Eina_Error ERROR_DISPATCH__err; \
if (!(_cbs)->error) (_ret) = (_value); \
else \
{ \
eina_value_get(&(_value), &ERROR_DISPATCH__err); \
(_ret) = (_cbs)->error((_data), ERROR_DISPATCH__err); \
} \
} while (0)
#define EASY_FUTURE_DISPATCH(_ret, _value, _dead_future, _cbs, _data) \
do { \
if ((_value).type == EINA_VALUE_TYPE_ERROR) ERROR_DISPATCH((_cbs), (_ret), (_value), (_data)); \
else \
{ \
if ((!(_cbs)->success_type) || ((_cbs)->success_type && (_value).type == (_cbs)->success_type)) \
{ \
if (!(_cbs)->success) (_ret) = (_value); /* pass thru */ \
else (_ret) = (_cbs)->success((_data), (_value)); \
} \
else \
{ \
Eina_Value EASY_FUTURE_DISPATCH__err = EINA_VALUE_EMPTY; \
ERR("Future %p, success cb: %p data: %p, expected success_type %p (%s), got %p (%s)", \
_dead_future, (_cbs)->success, (_data), \
(_cbs)->success_type, eina_value_type_name_get((_cbs)->success_type), \
(_value).type, (_value).type ? eina_value_type_name_get((_value).type) : NULL); \
if (eina_value_setup(&EASY_FUTURE_DISPATCH__err, EINA_VALUE_TYPE_ERROR)) eina_value_set(&EASY_FUTURE_DISPATCH__err, EINVAL); \
ERROR_DISPATCH((_cbs), (_ret), EASY_FUTURE_DISPATCH__err, (_data)); \
} \
} \
if ((_cbs)->free) (_cbs)->free((_data), _dead_future); \
} while(0)
#endif

View File

@ -340,6 +340,251 @@ EOAPI Eina_Bool efl_event_callback_legacy_call(Eo *obj, const Efl_Event_Descript
*/
EOAPI Eina_Bool efl_future_link(Eo *obj, Efl_Future *link);
/**
* @struct _Efl_Future_Cb_Desc
*
* A struct with callbacks to be used by efl_future_cb_from_desc() and efl_future_chain_array()
*
* @see efl_future_cb_from_desc()
* @see efl_future_chain_array()
*/
typedef struct _Efl_Future_Cb_Desc {
/**
* Called on success (value.type is not @c EINA_VALUE_TYPE_ERROR).
*
* if @c success_type is not NULL, then the value is guaranteed to be of that type,
* if it's not, then it will trigger @c error with @c EINVAL.
*
* After this function returns, @c free callback is called if provided.
*
* @note This function is always called from a safe context (main loop or some platform defined safe context).
*
* @param o The object used to create the link in efl_future_cb_from_desc() or efl_future_chain_array().
* @param value The operation result
* @return An Eina_Value to pass to the next Eina_Future in the chain (if any).
* If there is no need to convert the received value, it's @b recommended
* to pass-thru @p value argument. If you need to convert to a different type
* or generate a new value, use @c eina_value_setup() on @b another Eina_Value
* and return it. By returning an promise Eina_Value (eina_promise_as_value()) the
* whole chain will wait until the promise is resolved in
* order to continue its execution.
* Note that the value contents must survive this function scope,
* that is, do @b not use stack allocated blobs, arrays, structures or types that
* keeps references to memory you give. Values will be automatically cleaned up
* using @c eina_value_flush() once they are unused (no more future or futures
* returned a new value).
*/
Eina_Value (*success)(Eo *o, const Eina_Value value);
/**
* Called on error (value.type is @c EINA_VALUE_TYPE_ERROR).
*
* This function can return another error, propagating or converting it. However it
* may also return a non-error, in this case the next future in chain will receive a regular
* value, which may call its @c success.
*
* If this function is not provided, then it will pass thru the error to the next error handler.
*
* It may be called with @c EINVAL if @c success_type is provided and doesn't
* match the received type.
*
* It may be called with @c ECANCELED if future was canceled.
*
* It may be called with @c ENOMEM if memory allocation failed during callback creation.
*
* After this function returns, @c free callback is called if provided.
*
* @note On future creation errors and future cancellation this function will be called
* from the current context with the following errors respectitally: `EINVAL`, `ENOMEM` and `ECANCELED`.
* Otherwise this function is called from a safe context.
*
*
* @param o The object used to create the link in efl_future_cb_from_desc() or efl_future_chain_array().
* @param error The operation error
* @return An Eina_Value to pass to the next Eina_Future in the chain (if any).
* If you need to convert to a different type or generate a new value,
* use @c eina_value_setup() on @b another Eina_Value
* and return it. By returning an promise Eina_Value (eina_promise_as_value()) the
* whole chain will wait until the promise is resolved in
* order to continue its execution.
* Note that the value contents must survive this function scope,
* that is, do @b not use stack allocated blobs, arrays, structures or types that
* keeps references to memory you give. Values will be automatically cleaned up
* using @c eina_value_flush() once they are unused (no more future or futures
* returned a new value).
*/
Eina_Value (*error)(Eo *o, Eina_Error error);
/**
* Called on @b all situations to notify future destruction.
*
* This is called after @c success or @c error, as well as it's called if none of them are
* provided. Thus can be used as a "weak ref" mechanism.
*
* @note On future creation errors and future cancellation this function will be called
* from the current context with the following errors respectitally: `EINVAL`, `ENOMEM` and `ECANCELED`.
* Otherwise this function is called from a safe context.
*
* @param o The object used to create the link in efl_future_cb_from_desc() or efl_future_chain_array().
* @param dead_future The future that was freed.
*/
void (*free)(Eo *o, const Eina_Future *dead_future);
/**
* If provided, then @c success will only be called if the value type matches the given pointer.
*
* If provided and doesn't match, then @c error will be called with @c EINVAL. If no @c error,
* then it will be propagated to the next future in the chain.
*/
const Eina_Value_Type *success_type;
/**
* This is used by Eo to cancel a pending futures in case
* an Eo object is deleted. It can be @c NULL.
*/
Eina_Future **storage;
} Efl_Future_Cb_Desc;
/**
* Creates an Eina_Future_Desc for an EO object.
*
* This function creates an Eina_Future_Desc based on an Efl_Future_Cb_Desc.
* The main purpose of this function is create a "link" between the future
* and the object. In case the object is deleted before the future is resolved/rejected,
* the object destructor will cancel the future.
*
* @note In case context info are needed for the #Efl_Future_Desc callbacks efl_key_data_set()
* can be used.
*
* The example below shows a file download using an Eo object, if the download
* lasts more than 30 seconds the Eo object will be deleted, causing the
* future to also be deleted.
* Usually this would be done with an eina_future_race() of the download promise and a timeout promise,
* however we provide the following example to illustrate efl_key_data_set() usage.
*
* @code
*
* static Eina_Bool
* _timeout(void *data)
* {
* Eo *downloader = data;
* //In case the download is not completed yet.
* //Delete the downloader (which in cancel the file download and the future)
* efl_key_data_set(downloader, "timer", NULL);
* efl_unref(downloader);
* return EINA_FALSE;
* }
*
* static Eina_Value
* _file_ok(Eo *o EINA_UNUSED, const Eina_Value value)
* {
* const char *data;
* //There's no need to check the value type since EO infra already did that for us
* eina_value_get(&value, &data);
* //Deliver the data to the user
* data_deliver(data);
* return v;
* }
*
* static Eina_Value
* _file_err(Eo *o EINA_UNUSED, Eina_Error error)
* {
* //In case the downloader is deleted before the future is resolved, the future will be canceled thus this callback will be called.
* fprintf(stderr, "Could not download the file. Reason: %s\n", eina_error_msg_get(error));
* return EINA_VALUE_EMPTY;
* }
*
* static void
* _downlader_free(Eo *o, const Eina_Future *dead_future EINA_UNUSED)
* {
* Ecore_Timer *t = efl_key_data_get(o, "timer");
* //The download was finished before the timer expired. Cancel it...
* if (t)
* {
* ecore_timer_del(t);
* efl_unref(o); //Delete the object
* } //else - In this case the future was canceled due efl_unref() in _timeout - No need to call efl_unref()
* }
*
* void download_file(const char *file)
* {
* //This could be rewritten using eina_future_race()
* Eo *downloader = efl_add(MY_DOWNLOADER_CLASS, NULL);
* Eina_Future *f = downloader_download_file(downloader, file);
* timer = ecore_timer_add(30, _timeout, downloader);
* //Usually this would be done with an eina_future_race() of the download promise and a timeout promise,
* //however we provide the following example to illustrate efl_key_data_set() usage.
* efl_key_data_set(downloader, "timer", timer);
* eina_future_then_from_desc(f, efl_future_cb(.success = _file_ok, .error = _file_err, .success_type = EINA_VALUE_TYPE_STRING, .free = downloader_free));
* }
* @endcode
*
* @param obj The object to create the link.
* @param desc An Efl_Future_Cb_Desc
* @return An Eina_Future_Desc to be used by eina_future_then(), eina_future_chain() and friends.
* @see efl_future_chain_array()
* @see efl_future_cb()
* @see #Efl_Future_Cb_Desc
* @see efl_key_data_set()
*/
EOAPI Eina_Future_Desc efl_future_cb_from_desc(Eo *obj, const Efl_Future_Cb_Desc desc);
/**
* Syntax suger over efl_future_cb_from_desc()
*
* Usage:
* @code
* eina_future_then_from_desc(future, efl_future_cb(my_object, .succes = success, .success_type = EINA_VALUE_TYPE_INT));
* @endcode
*
* @see efl_future_cb_from_desc()
* @see efl_future_Eina_FutureXXX_then()
*/
#define efl_future_cb(_eo, ...) efl_future_cb_from_desc(_eo, (Efl_Future_Cb_Desc){__VA_ARGS__})
/**
* Syntax sugar over eina_future_then_from_desc() and efl_future_cb().
*
* Usage:
* @code
* efl_future_Eina_FutureXXX_then(o, future, .success = success, .success_type = EINA_VALUE_TYPE_INT);
* @endcode
*
*/
#define efl_future_Eina_FutureXXX_then(_eo, _future, ...) eina_future_then_from_desc(_future, efl_future_cb(_eo, ## __VA_ARGS__))
/**
* Creates an Future chain based on #Efl_Future_Cb_Desc
*
* This function is an wrapper around efl_future_cb_from_desc() and eina_future_then_from_desc()
*
* For more information about them, check their documentations.
*
*
* @param obj An EO object to link against the future
* @param prev The previous future
* @param descs An array of Efl_Future_Cb_Desc
* @return An Eina_Future or @c NULL on error.
* @note If an error happens the whole future chain will be CANCELED, causing
* desc.error to be called passing `ENOMEM` or `EINVAL` and desc.free
* to free the @p obj if necessary.
*
* @see efl_future_chain()
* @see efl_future_cb()
* @see eina_future_then_from_desc()
* @see #Efl_Future_Cb_Desc
*/
EOAPI Eina_Future *efl_future_chain_array(Eo *obj, Eina_Future *prev, const Efl_Future_Cb_Desc descs[]);
/**
* Syntax suger over efl_future_chain_array()
*
* Usage:
* @code
* Eina_Future *f = efl_future_chain(my_object, prev_future, {}, {});
* @endcode
*
* @see efl_future_chain_array()
*/
#define efl_future_chain(_eo, _prev, ...) efl_future_chain_array(_eo, _prev, (Efl_Future_Cb_Desc []){__VA_ARGS__, {NULL, NULL, NULL, NULL, NULL}})
/**
* @addtogroup Eo_Debug_Information Eo's Debug information helper.
* @{
@ -1806,6 +2051,8 @@ efl_replace(Eo **storage, Eo *new_obj)
*storage = new_obj;
}
EOAPI extern const Eina_Value_Type *EINA_VALUE_TYPE_OBJECT;
/**
* @}
*/

View File

@ -62,3 +62,11 @@ struct @extern Eina.Rw_Slice {
}
struct @extern Eina.Value.Type; [[Eina value type]]
struct @extern Eina.Future; [[Eina Future handles]]
struct @extern Eina.Future.Scheduler; [[This struct is used as a bridge between Eina and the future scheduler.
By using the provided functions Eina can schedule futures resolutions,
rejections and cancelations to a safe context.]]
struct @extern Eina.Coro; [[Eina Coro (coroutine) handle.]]

View File

@ -2137,6 +2137,118 @@ _eo_table_del_cb(void *in)
* This is used by the gdb debug helper script */
Eo_Id_Data *_eo_gdb_main_domain = NULL;
typedef struct _Eo_Coro_Hook_Data
{
EINA_INLIST;
const Eina_Coro *coro;
Efl_Domain_Data *domain_data;
Efl_Id_Domain return_domain;
} Eo_Coro_Hook_Data;
static Eina_Inlist *_eo_coro_hook_data = NULL;
static Eina_Lock _eo_coro_hook_data_lock;
/* Flow:
*
* main_exit -> coro_enter -> coro_exit -> main_enter
*
* main_exit: efl_domain_data_get()
* coro_enter: efl_domain_data_adopt()
* coro_exit: efl_domain_data_return()
* main_enter: remove from list
*/
static Eo_Coro_Hook_Data *
_eo_coro_hook_data_find_unlocked(const Eina_Coro *coro)
{
Eo_Coro_Hook_Data *d;
EINA_INLIST_FOREACH(_eo_coro_hook_data, d)
{
if (d->coro == coro)
{
if (_eo_coro_hook_data != EINA_INLIST_GET(d))
_eo_coro_hook_data = eina_inlist_promote(_eo_coro_hook_data,
EINA_INLIST_GET(d));
return d;
}
}
return NULL;
}
static Eo_Coro_Hook_Data *
_eo_coro_hook_data_find(const Eina_Coro *coro)
{
Eo_Coro_Hook_Data *d;
eina_lock_take(&_eo_coro_hook_data_lock);
d = _eo_coro_hook_data_find_unlocked(coro);
eina_lock_release(&_eo_coro_hook_data_lock);
return d;
}
static Eina_Bool
_eo_coro_hook_main_exit(void *data EINA_UNUSED, const Eina_Coro *coro)
{
Eo_Coro_Hook_Data *d = malloc(sizeof(Eo_Coro_Hook_Data));
EINA_SAFETY_ON_NULL_RETURN_VAL(d, EINA_FALSE);
d->coro = coro;
d->domain_data = efl_domain_data_get();
d->return_domain = EFL_ID_DOMAIN_INVALID;
eina_lock_take(&_eo_coro_hook_data_lock);
_eo_coro_hook_data = eina_inlist_prepend(_eo_coro_hook_data, EINA_INLIST_GET(d));
eina_lock_release(&_eo_coro_hook_data_lock);
return EINA_TRUE;
}
static void
_eo_coro_hook_main_enter(void *data EINA_UNUSED, const Eina_Coro *coro)
{
Eo_Coro_Hook_Data *d;
eina_lock_take(&_eo_coro_hook_data_lock);
d = _eo_coro_hook_data_find_unlocked(coro);
if (d)
_eo_coro_hook_data = eina_inlist_remove(_eo_coro_hook_data, EINA_INLIST_GET(d));
eina_lock_release(&_eo_coro_hook_data_lock);
EINA_SAFETY_ON_NULL_RETURN(d); // just to print-out unexpected error.
free(d);
}
static Eina_Bool
_eo_coro_hook_coro_enter(void *data EINA_UNUSED, const Eina_Coro *coro)
{
Eo_Coro_Hook_Data *d = _eo_coro_hook_data_find(coro);
EINA_SAFETY_ON_NULL_RETURN_VAL(d, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(d->domain_data, EINA_FALSE);
EINA_SAFETY_ON_TRUE_RETURN_VAL(d->return_domain != EFL_ID_DOMAIN_INVALID, EINA_FALSE);
d->return_domain = efl_domain_data_adopt(d->domain_data);
EINA_SAFETY_ON_TRUE_RETURN_VAL(d->return_domain == EFL_ID_DOMAIN_INVALID, EINA_FALSE);
return EINA_TRUE;
}
static void
_eo_coro_hook_coro_exit(void *data EINA_UNUSED, const Eina_Coro *coro)
{
Eo_Coro_Hook_Data *d = _eo_coro_hook_data_find(coro);
EINA_SAFETY_ON_NULL_RETURN(d);
EINA_SAFETY_ON_NULL_RETURN(d->domain_data);
EINA_SAFETY_ON_TRUE_RETURN(d->return_domain == EFL_ID_DOMAIN_INVALID);
efl_domain_data_return(d->return_domain);
d->domain_data = NULL;
}
EAPI Eina_Bool
efl_object_init(void)
{
@ -2229,6 +2341,13 @@ efl_object_init(void)
_efl_add_fallback_init();
eina_lock_new(&_eo_coro_hook_data_lock);
eina_coro_hook_add(_eo_coro_hook_coro_enter,
_eo_coro_hook_coro_exit,
_eo_coro_hook_main_enter,
_eo_coro_hook_main_exit,
NULL);
eina_log_timing(_eo_log_dom,
EINA_LOG_STATE_STOP,
EINA_LOG_STATE_INIT);
@ -2252,6 +2371,13 @@ efl_object_shutdown(void)
EINA_LOG_STATE_START,
EINA_LOG_STATE_SHUTDOWN);
eina_coro_hook_del(_eo_coro_hook_coro_enter,
_eo_coro_hook_coro_exit,
_eo_coro_hook_main_enter,
_eo_coro_hook_main_exit,
NULL);
eina_lock_free(&_eo_coro_hook_data_lock);
_efl_add_fallback_shutdown();
efl_future_shutdown();
@ -3182,3 +3308,98 @@ eo_objects_iterator_new(void)
return (Eina_Iterator *)it;
}
static Eina_Bool
_eo_value_setup(const Eina_Value_Type *type EINA_UNUSED, void *mem)
{
Eo **tmem = mem;
*tmem = NULL;
return EINA_TRUE;
}
static Eina_Bool
_eo_value_flush(const Eina_Value_Type *type EINA_UNUSED, void *mem)
{
Eo **tmem = mem;
if (*tmem)
{
efl_unref(*tmem);
*tmem = NULL;
}
return EINA_TRUE;
}
static void
_eo_value_replace(Eo **dst, Eo * const *src)
{
if (*src == *dst) return;
//ref *src first, since efl_unref(*dst) may trigger *src unref()
efl_ref(*src);
efl_unref(*dst);
*dst = *src;
}
static Eina_Bool
_eo_value_vset(const Eina_Value_Type *type EINA_UNUSED, void *mem, va_list args)
{
Eo **dst = mem;
Eo **src = va_arg(args, Eo **);
_eo_value_replace(dst, src);
return EINA_TRUE;
}
static Eina_Bool
_eo_value_pset(const Eina_Value_Type *type EINA_UNUSED,
void *mem, const void *ptr)
{
Eo **dst = mem;
Eo * const *src = ptr;
_eo_value_replace(dst, src);
return EINA_TRUE;
}
static Eina_Bool
_eo_value_pget(const Eina_Value_Type *type EINA_UNUSED,
const void *mem, void *ptr)
{
Eo * const *src = mem;
Eo **dst = ptr;
*dst = *src;
return EINA_TRUE;
}
static Eina_Bool
_eo_value_convert_to(const Eina_Value_Type *type EINA_UNUSED, const Eina_Value_Type *convert, const void *type_mem, void *convert_mem)
{
Eo * const *eo = type_mem;
if (convert == EINA_VALUE_TYPE_STRINGSHARE ||
convert == EINA_VALUE_TYPE_STRING)
{
const char *other_mem;
char buf[256];
snprintf(buf, sizeof(buf), "Object id: %p, class: %s, name: %s",
*eo, efl_class_name_get(efl_class_get(*eo)),
efl_debug_name_get(*eo));
other_mem = buf;
return eina_value_type_pset(convert, convert_mem, &other_mem);
}
return EINA_FALSE;
}
static const Eina_Value_Type _EINA_VALUE_TYPE_OBJECT = {
.version = EINA_VALUE_TYPE_VERSION,
.value_size = sizeof(Eo *),
.name = "Efl_Object",
.setup = _eo_value_setup,
.flush = _eo_value_flush,
.copy = NULL,
.compare = NULL,
.convert_to = _eo_value_convert_to,
.convert_from = NULL,
.vset = _eo_value_vset,
.pset = _eo_value_pset,
.pget = _eo_value_pget
};
EOAPI const Eina_Value_Type *EINA_VALUE_TYPE_OBJECT = &_EINA_VALUE_TYPE_OBJECT;

View File

@ -8,6 +8,7 @@
#include "Eo.h"
#include "eo_ptr_indirection.h"
#include "eo_private.h"
#include "eina_promise_private.h"
#define EFL_EVENT_SPECIAL_SKIP 1
@ -45,6 +46,7 @@ typedef struct
Efl_Event_Callback_Frame *event_frame;
Eo_Callback_Description **callbacks;
Eina_Inlist *pending_futures;
unsigned int callbacks_count;
unsigned short event_freeze_count;
@ -79,6 +81,15 @@ typedef struct
Eo_Generic_Data_Node_Type d_type;
} Eo_Generic_Data_Node;
typedef struct _Efl_Future_Pending
{
EINA_INLIST;
Eo *o;
Eina_Future *future;
Efl_Future_Cb_Desc desc;
} Efl_Future_Pending;
typedef struct
{
EINA_INLIST;
@ -972,48 +983,77 @@ struct _Eo_Callback_Description
static int _eo_callbacks = 0;
static Eina_Mempool *_eo_callback_mempool = NULL;
static int _efl_pending_futures = 0;
static Eina_Mempool *_efl_pending_future_mempool = NULL;
static void
_eo_callback_free(Eo_Callback_Description *cb)
_mempool_data_free(Eina_Mempool **mp, int *usage, void *data)
{
if (!cb) return;
eina_mempool_free(_eo_callback_mempool, cb);
_eo_callbacks--;
if (_eo_callbacks == 0)
if (!data) return;
eina_mempool_free(*mp, data);
(*usage)--;
if (*usage == 0)
{
eina_mempool_del(_eo_callback_mempool);
_eo_callback_mempool = NULL;
eina_mempool_del(*mp);
*mp = NULL;
}
}
static Eo_Callback_Description *
_eo_callback_new(void)
static void *
_mempool_data_alloc(Eina_Mempool **mp, int *usage, size_t size)
{
Eo_Callback_Description *cb;
// very unlikely that the mempool isnt initted, so take all the init code
// and move it out of l1 instruction cache space so we dont pollute the
// l1 cache with unused code 99% of the time
if (!_eo_callback_mempool) goto init_mempool;
if (!*mp) goto init_mempool;
init_mempool_back:
cb = eina_mempool_calloc(_eo_callback_mempool,
sizeof(Eo_Callback_Description));
cb = eina_mempool_calloc(*mp, size);
if (cb)
{
_eo_callbacks++;
(*usage)++;
return cb;
}
if (_eo_callbacks != 0) return NULL;
eina_mempool_del(_eo_callback_mempool);
_eo_callback_mempool = NULL;
if (*usage != 0) return NULL;
eina_mempool_del(*mp);
*mp = NULL;
return NULL;
init_mempool:
_eo_callback_mempool = eina_mempool_add
("chained_mempool", NULL, NULL, sizeof(Eo_Callback_Description), 256);
if (!_eo_callback_mempool) return NULL;
*mp = eina_mempool_add
("chained_mempool", NULL, NULL, size, 256);
if (!*mp) return NULL;
goto init_mempool_back;
}
static void
_eo_callback_free(Eo_Callback_Description *cb)
{
_mempool_data_free(&_eo_callback_mempool, &_eo_callbacks, cb);
}
static Eo_Callback_Description *
_eo_callback_new(void)
{
return _mempool_data_alloc(&_eo_callback_mempool, &_eo_callbacks,
sizeof(Eo_Callback_Description));
}
static void
_efl_pending_future_free(Efl_Future_Pending *pending)
{
_mempool_data_free(&_efl_pending_future_mempool,
&_efl_pending_futures, pending);
}
static Efl_Future_Pending *
_efl_pending_future_new(void)
{
return _mempool_data_alloc(&_efl_pending_future_mempool,
&_efl_pending_futures,
sizeof(Efl_Future_Pending));
}
#ifdef EFL_EVENT_SPECIAL_SKIP
#define CB_COUNT_INC(cnt) do { if ((cnt) != 0xffff) (cnt)++; } while(0)
@ -1858,6 +1898,104 @@ EAPI const Eina_Value_Type *EFL_DBG_INFO_TYPE = &_EFL_DBG_INFO_TYPE;
/* EFL_OBJECT_CLASS stuff */
#define MY_CLASS EFL_OBJECT_CLASS
static void
_efl_pending_futures_clear(Efl_Object_Data *pd)
{
while (pd->pending_futures)
{
Efl_Future_Pending *pending = EINA_INLIST_CONTAINER_GET(pd->pending_futures, Efl_Future_Pending);
Eina_Future *future = *pending->desc.storage;
assert(future);
eina_future_cancel(future);
}
}
static Eina_Value
_efl_future_cb(void *data, const Eina_Value value, const Eina_Future *dead_future)
{
Efl_Future_Pending *pending = data;
Eina_Value ret = value;
Eo *o;
Efl_Object_Data *pd;
EINA_SAFETY_ON_NULL_GOTO(pending, err);
o = pending->o;
pd = efl_data_scope_get(o, EFL_OBJECT_CLASS);
EINA_SAFETY_ON_NULL_GOTO(pd, err);
pd->pending_futures = eina_inlist_remove(pd->pending_futures,
EINA_INLIST_GET(pending));
efl_ref(o);
EASY_FUTURE_DISPATCH(ret, value, dead_future, &pending->desc, o);
efl_unref(o);
_efl_pending_future_free(pending);
return ret;
err:
eina_value_setup(&ret, EINA_VALUE_TYPE_ERROR);
eina_value_set(&ret, ENOMEM);
return ret;
}
EOAPI Eina_Future_Desc
efl_future_cb_from_desc(Eo *o, const Efl_Future_Cb_Desc desc)
{
Efl_Future_Pending *pending = NULL;
Eina_Future **storage = NULL;
Efl_Object_Data *pd;
EINA_SAFETY_ON_NULL_GOTO(o, end);
pd = efl_data_scope_get(o, EFL_OBJECT_CLASS);
EINA_SAFETY_ON_NULL_GOTO(pd, end);
pending = _efl_pending_future_new();
EINA_SAFETY_ON_NULL_GOTO(pending, end);
memcpy(&pending->desc, &desc, sizeof(Efl_Future_Cb_Desc));
pending->o = o;
pending->future = NULL;
if (!pending->desc.storage) pending->desc.storage = &pending->future;
pd->pending_futures = eina_inlist_append(pd->pending_futures,
EINA_INLIST_GET(pending));
storage = pending->desc.storage;
end:
return (Eina_Future_Desc){ .cb = _efl_future_cb, .data = pending, .storage = storage };
}
EOAPI Eina_Future *
efl_future_chain_array(Eo *obj,
Eina_Future *prev,
const Efl_Future_Cb_Desc descs[])
{
ssize_t i = -1;
Eina_Future *f = prev;
for (i = 0; descs[i].success || descs[i].error || descs[i].free || descs[i].success_type; i++)
{
Eina_Future_Desc eina_desc = efl_future_cb_from_desc(obj, descs[i]);
f = eina_future_then_from_desc(f, eina_desc);
EINA_SAFETY_ON_NULL_GOTO(f, err);
}
return f;
err:
/*
There's no need to cancel the futures, since eina_future_then_from_desc()
will cancel the whole chain in case of failure.
All we need to do is to free the remaining descs
*/
for (i = i + 1; descs[i].error || descs[i].free; i++)
{
if (descs[i].error)
{
Eina_Value r = descs[i].error(obj, ENOMEM);
if (r.type) eina_value_flush(&r);
}
if (descs[i].free) descs[i].free(obj, NULL);
}
return NULL;
}
EOLIAN static Eo *
_efl_object_constructor(Eo *obj, Efl_Object_Data *pd EINA_UNUSED)
{
@ -1905,6 +2043,7 @@ composite_obj_back:
if (pd->parent) goto err_parent;
err_parent_back:
_efl_pending_futures_clear(pd);
_eo_generic_data_del_all(obj, pd);
_wref_destruct(pd);
_eo_callback_remove_all(pd);

View File

@ -29,6 +29,7 @@ static const Efl_Test_Case etc[] = {
{ "Ecore_Promise", ecore_test_ecore_promise },
{ "Ecore_Job", ecore_test_ecore_job },
{ "Ecore_Args", ecore_test_ecore_args },
{ "Ecore_Promise2", ecore_test_ecore_promise2 },
{ NULL, NULL }
};

View File

@ -18,5 +18,6 @@ void ecore_test_ecore_file(TCase *tc);
void ecore_test_ecore_promise(TCase *tc);
void ecore_test_ecore_job(TCase *tc);
void ecore_test_ecore_args(TCase *tc);
void ecore_test_ecore_promise2(TCase *tc);
#endif /* _ECORE_SUITE_H */

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -0,0 +1,562 @@
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <Eina.h>
#include "eina_suite.h"
struct ctx {
int a, b;
};
#define VAL_A 1234
#define VAL_B 4567
#define RETVAL (void*)8901
#define CANCELVAL (void*)0xCA
#define COUNT 100
#ifdef EINA_SAFETY_CHECKS
struct log_ctx {
const char *msg;
const char *fnc;
int level;
Eina_Bool did;
Eina_Bool just_fmt;
};
/* tests should not output on success, just uncomment this for debugging */
//#define SHOW_LOG 1
static void
_eina_test_safety_print_cb(const Eina_Log_Domain *d, Eina_Log_Level level, const char *file, const char *fnc, int line, const char *fmt, void *data, va_list args EINA_UNUSED)
{
struct log_ctx *ctx = data;
va_list cp_args;
const char *str;
va_copy(cp_args, args);
str = va_arg(cp_args, const char *);
va_end(cp_args);
ck_assert_int_eq(level, ctx->level);
if (ctx->just_fmt)
ck_assert_str_eq(fmt, ctx->msg);
else
{
ck_assert_str_eq(fmt, "%s");
ck_assert_str_eq(ctx->msg, str);
}
ck_assert_str_eq(ctx->fnc, fnc);
ctx->did = EINA_TRUE;
#ifdef SHOW_LOG
eina_log_print_cb_stderr(d, level, file, fnc, line, fmt, NULL, args);
#else
(void)d;
(void)file;
(void)line;
#endif
}
#endif
static const void *
coro_func_noyield(void *data, Eina_Bool canceled, Eina_Coro *coro EINA_UNUSED)
{
struct ctx *ctx = data;
ck_assert_ptr_nonnull(ctx);
ck_assert_int_eq(ctx->a, VAL_A);
if (canceled) return CANCELVAL;
ctx->b = VAL_B;
return RETVAL;
}
START_TEST(coro_noyield)
{
Eina_Coro *coro;
struct ctx ctx = {
.a = VAL_A,
.b = 0,
};
void *result = NULL;
int i = 0;
eina_init();
coro = eina_coro_new(coro_func_noyield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
ck_assert_ptr_nonnull(coro);
while (eina_coro_run(&coro, &result, NULL))
{
i++;
ck_assert_int_le(i, 1);
}
ck_assert_ptr_null(coro);
ck_assert_int_eq(ctx.b, VAL_B);
ck_assert_ptr_eq(result, RETVAL);
eina_shutdown();
}
END_TEST
static const void *
coro_func_yield(void *data, Eina_Bool canceled, Eina_Coro *coro)
{
struct ctx *ctx = data;
char buf[256] = "hi there";
int i;
ck_assert_ptr_nonnull(ctx);
ck_assert_int_eq(ctx->a, VAL_A);
if (canceled) return CANCELVAL;
ctx->b = 1;
eina_coro_yield_or_return(coro, CANCELVAL);
ctx->b = 2;
eina_coro_yield_or_return(coro, CANCELVAL);
for (i = 0; i < COUNT; i++) {
ctx->b = i * 10;
/* have some stuff on stack and write to it, so we validate
* non-thread based solutions are really saving their stack.
*/
snprintf(buf, sizeof(buf), "b=%d -----------------", ctx->b);
eina_coro_yield_or_return(coro, CANCELVAL);
}
ctx->b = VAL_B;
return RETVAL;
}
START_TEST(coro_yield)
{
Eina_Coro *coro;
struct ctx ctx = {
.a = VAL_A,
.b = 0,
};
Eina_Bool r;
void *result = NULL;
int i = 0;
eina_init();
coro = eina_coro_new(coro_func_yield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
ck_assert_ptr_nonnull(coro);
r = eina_coro_run(&coro, NULL, NULL);
ck_assert_int_eq(r, EINA_TRUE);
ck_assert_int_eq(ctx.b, 1);
r = eina_coro_run(&coro, NULL, NULL);
ck_assert_int_eq(r, EINA_TRUE);
ck_assert_int_eq(ctx.b, 2);
while (eina_coro_run(&coro, &result, NULL))
{
ck_assert_int_eq(ctx.b, i * 10);
i++;
ck_assert_int_le(i, COUNT);
/* change caller's stack to guarantee coroutine stack is
* being properly persisted.
*/
memset(alloca(10), 0xff, 10);
}
ck_assert_ptr_null(coro);
ck_assert_int_eq(i, COUNT);
ck_assert_int_eq(ctx.b, VAL_B);
ck_assert_ptr_eq(result, RETVAL);
eina_shutdown();
}
END_TEST
START_TEST(coro_cancel)
{
Eina_Coro *coro;
struct ctx ctx = {
.a = VAL_A,
.b = 0,
};
Eina_Bool r;
eina_init();
// cancel before it runs
coro = eina_coro_new(coro_func_yield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
ck_assert_ptr_nonnull(coro);
ck_assert_ptr_eq(eina_coro_cancel(&coro), CANCELVAL);
ck_assert_ptr_null(coro);
// cancel after single run
coro = eina_coro_new(coro_func_yield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
ck_assert_ptr_nonnull(coro);
r = eina_coro_run(&coro, NULL, NULL);
ck_assert_int_eq(r, EINA_TRUE);
ck_assert_int_eq(ctx.b, 1);
ck_assert_ptr_eq(eina_coro_cancel(&coro), CANCELVAL);
ck_assert_ptr_null(coro);
ck_assert_int_eq(ctx.b, 1); // it's yielding after setting b=1
eina_shutdown();
}
END_TEST
static Eina_Bool
_coro_hook_enter_success(void *data, const Eina_Coro *coro EINA_UNUSED)
{
int *i = data;
(*i) += 1;
return EINA_TRUE;
}
static void
_coro_hook_exit(void *data, const Eina_Coro *coro EINA_UNUSED)
{
int *i = data;
(*i) += 100;
}
static void
_main_hook_enter(void *data, const Eina_Coro *coro EINA_UNUSED)
{
int *i = data;
(*i) += 10;
}
static Eina_Bool
_main_hook_exit_success(void *data, const Eina_Coro *coro EINA_UNUSED)
{
int *i = data;
(*i) += 1000;
return EINA_TRUE;
}
START_TEST(coro_hook)
{
Eina_Coro *coro;
struct ctx ctx = {
.a = VAL_A,
.b = 0,
};
void *result = NULL;
int i = 0, hooks_result = 0;
eina_init();
fail_unless(eina_coro_hook_add(_coro_hook_enter_success,
_coro_hook_exit,
_main_hook_enter,
_main_hook_exit_success,
&hooks_result));
coro = eina_coro_new(coro_func_noyield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
ck_assert_ptr_nonnull(coro);
while (eina_coro_run(&coro, &result, NULL))
{
i++;
ck_assert_int_le(i, 1);
}
ck_assert_ptr_null(coro);
ck_assert_int_eq(ctx.b, VAL_B);
ck_assert_ptr_eq(result, RETVAL);
fail_unless(eina_coro_hook_del(_coro_hook_enter_success,
_coro_hook_exit,
_main_hook_enter,
_main_hook_exit_success,
&hooks_result));
ck_assert_int_eq(hooks_result, 1111);
eina_shutdown();
}
END_TEST
#ifdef EINA_SAFETY_CHECKS
START_TEST(coro_new_null)
{
Eina_Coro *coro;
eina_init();
#ifdef SHOW_LOG
fprintf(stderr, "you should have a safety check failure below:\n");
#endif
struct log_ctx lctx;
#define TEST_MAGIC_SAFETY(fn, _msg) \
lctx.msg = _msg; \
lctx.fnc = fn; \
lctx.just_fmt = EINA_FALSE; \
lctx.level = EINA_LOG_LEVEL_ERR; \
lctx.did = EINA_FALSE
eina_log_print_cb_set(_eina_test_safety_print_cb, &lctx);
TEST_MAGIC_SAFETY("eina_coro_new", "safety check failed: func == NULL");
coro = eina_coro_new(NULL, NULL, EINA_CORO_STACK_SIZE_DEFAULT);
ck_assert_ptr_null(coro);
fail_unless(lctx.did);
eina_log_print_cb_set(eina_log_print_cb_stderr, NULL);
#undef TEST_MAGIC_SAFETY
eina_shutdown();
}
END_TEST
START_TEST(coro_yield_incorrect)
{
Eina_Coro *coro;
struct ctx ctx = {
.a = VAL_A,
.b = 0,
};
void *result = NULL;
int i = 0;
eina_init();
#ifdef SHOW_LOG
fprintf(stderr, "you should have a safety check failure below:\n");
#endif
struct log_ctx lctx;
#define TEST_MAGIC_SAFETY(fn, _msg) \
lctx.msg = _msg; \
lctx.fnc = fn; \
lctx.just_fmt = EINA_TRUE; \
lctx.level = EINA_LOG_LEVEL_CRITICAL; \
lctx.did = EINA_FALSE
eina_log_print_cb_set(_eina_test_safety_print_cb, &lctx);
coro = eina_coro_new(coro_func_noyield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
ck_assert_ptr_nonnull(coro);
TEST_MAGIC_SAFETY("eina_coro_yield", "must be called from coroutine! coro=%p {func=%p data=%p turn=%s threads={%p%c %p%c} awaiting=%p}");
fail_if(eina_coro_yield(coro));
fail_unless(lctx.did);
eina_log_print_cb_set(eina_log_print_cb_stderr, NULL);
#undef TEST_MAGIC_SAFETY
while (eina_coro_run(&coro, &result, NULL))
{
i++;
ck_assert_int_le(i, 1);
}
ck_assert_ptr_null(coro);
ck_assert_int_eq(ctx.b, VAL_B);
ck_assert_ptr_eq(result, RETVAL);
eina_shutdown();
}
END_TEST
static const void *
coro_func_run_incorrect(void *data, Eina_Bool canceled, Eina_Coro *coro)
{
#ifdef SHOW_LOG
fprintf(stderr, "you should have a safety check failure below:\n");
#endif
struct log_ctx lctx;
#define TEST_MAGIC_SAFETY(fn, _msg) \
lctx.msg = _msg; \
lctx.fnc = fn; \
lctx.just_fmt = EINA_TRUE; \
lctx.level = EINA_LOG_LEVEL_CRITICAL; \
lctx.did = EINA_FALSE
eina_log_print_cb_set(_eina_test_safety_print_cb, &lctx);
TEST_MAGIC_SAFETY("eina_coro_run", "must be called from main thread! coro=%p {func=%p data=%p turn=%s threads={%p%c %p%c} awaiting=%p}");
fail_if(eina_coro_run(&coro, NULL, NULL));
fail_unless(lctx.did);
eina_log_print_cb_set(eina_log_print_cb_stderr, NULL);
#undef TEST_MAGIC_SAFETY
return coro_func_noyield(data, canceled, coro);
}
START_TEST(coro_run_incorrect)
{
Eina_Coro *coro;
struct ctx ctx = {
.a = VAL_A,
.b = 0,
};
void *result = NULL;
int i = 0;
eina_init();
coro = eina_coro_new(coro_func_run_incorrect, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
ck_assert_ptr_nonnull(coro);
while (eina_coro_run(&coro, &result, NULL))
{
i++;
ck_assert_int_le(i, 1);
}
ck_assert_ptr_null(coro);
ck_assert_int_eq(ctx.b, VAL_B);
ck_assert_ptr_eq(result, RETVAL);
eina_shutdown();
}
END_TEST
static Eina_Bool
_coro_hook_enter_failed(void *data, const Eina_Coro *coro EINA_UNUSED)
{
int *i = data;
(*i) += 2;
return EINA_FALSE;
}
static Eina_Bool
_main_hook_exit_failed(void *data, const Eina_Coro *coro EINA_UNUSED)
{
int *i = data;
(*i) += 2000;
return EINA_FALSE;
}
START_TEST(coro_hook_failed)
{
Eina_Coro *coro;
struct ctx ctx = {
.a = VAL_A,
.b = 0,
};
void *result = NULL;
int i = 0, hooks_result = 0;
eina_init();
#ifdef SHOW_LOG
fprintf(stderr, "you should have a safety check failure below:\n");
#endif
struct log_ctx lctx;
#define TEST_MAGIC_SAFETY(fn, _msg) \
lctx.msg = _msg; \
lctx.fnc = fn; \
lctx.just_fmt = EINA_TRUE; \
lctx.level = EINA_LOG_LEVEL_ERR; \
lctx.did = EINA_FALSE
eina_log_print_cb_set(_eina_test_safety_print_cb, &lctx);
fail_unless(eina_coro_hook_add(_coro_hook_enter_failed,
_coro_hook_exit,
_main_hook_enter,
_main_hook_exit_success,
&hooks_result));
coro = eina_coro_new(coro_func_noyield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
ck_assert_ptr_nonnull(coro);
TEST_MAGIC_SAFETY("_eina_coro_hooks_coro_enter", "failed hook enter=%p data=%p for coroutine coro=%p {func=%p data=%p turn=%s threads={%p%c %p%c} awaiting=%p}");
while (eina_coro_run(&coro, &result, NULL))
{
i++;
ck_assert_int_le(i, 1);
}
ck_assert_ptr_null(coro);
ck_assert_int_eq(ctx.b, 0);
ck_assert_ptr_eq(result, CANCELVAL);
fail_unless(lctx.did);
fail_unless(eina_coro_hook_del(_coro_hook_enter_failed,
_coro_hook_exit,
_main_hook_enter,
_main_hook_exit_success,
&hooks_result));
ck_assert_int_eq(hooks_result, 1112);
// now fail main exit
hooks_result = 0;
fail_unless(eina_coro_hook_add(_coro_hook_enter_success,
_coro_hook_exit,
_main_hook_enter,
_main_hook_exit_failed,
&hooks_result));
coro = eina_coro_new(coro_func_noyield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
ck_assert_ptr_nonnull(coro);
TEST_MAGIC_SAFETY("_eina_coro_hooks_main_exit", "failed hook exit=%p data=%p for main routine coro=%p {func=%p data=%p turn=%s threads={%p%c %p%c} awaiting=%p}");
while (eina_coro_run(&coro, &result, NULL))
{
i++;
ck_assert_int_le(i, 1);
}
ck_assert_ptr_null(coro);
ck_assert_int_eq(ctx.b, 0);
ck_assert_ptr_eq(result, CANCELVAL);
fail_unless(lctx.did);
fail_unless(eina_coro_hook_del(_coro_hook_enter_success,
_coro_hook_exit,
_main_hook_enter,
_main_hook_exit_failed,
&hooks_result));
ck_assert_int_eq(hooks_result, 2111);
eina_log_print_cb_set(eina_log_print_cb_stderr, NULL);
#undef TEST_MAGIC_SAFETY
eina_shutdown();
}
END_TEST
#endif
void
eina_test_coro(TCase *tc)
{
tcase_add_test(tc, coro_noyield);
tcase_add_test(tc, coro_yield);
// coro_await is tested in ecore_suite, so it hooks into the main loop
tcase_add_test(tc, coro_cancel);
tcase_add_test(tc, coro_hook);
#ifdef EINA_SAFETY_CHECKS
tcase_add_test(tc, coro_new_null);
tcase_add_test(tc, coro_yield_incorrect);
tcase_add_test(tc, coro_run_incorrect);
tcase_add_test(tc, coro_hook_failed);
#endif
}

View File

@ -12,6 +12,7 @@ static const Efl_Test_Case etc[] = {
{ "Eo class behaviour errors", eo_test_class_behaviour_errors },
{ "Eo call errors", eo_test_call_errors },
{ "Eo eina value", eo_test_value },
{ "Eo eina coroutines", eo_test_coro },
{ "Eo threaded eo calls", eo_test_threaded_calls },
{ "Eo event calls", eo_test_event},
{ NULL, NULL }

View File

@ -9,6 +9,7 @@ void eo_test_class_errors(TCase *tc);
void eo_test_class_behaviour_errors(TCase *tc);
void eo_test_call_errors(TCase *tc);
void eo_test_value(TCase *tc);
void eo_test_coro(TCase *tc);
void eo_test_threaded_calls(TCase *tc);
void eo_test_event(TCase *tc);

View File

@ -0,0 +1,52 @@
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdio.h>
#include <Eo.h>
#include "eo_suite.h"
#include "eo_test_class_simple.h"
#define RETVAL ((void *)0x1234)
static const void *
func(void *data, Eina_Bool canceled EINA_UNUSED, Eina_Coro *coro EINA_UNUSED)
{
Eo *obj = data;
fail_unless(efl_ref(obj)); // if efl_domain_data is wrong, this fails...
return RETVAL;
}
START_TEST(eo_coro)
{
Eo *obj;
Eina_Coro *coro;
void *result;
efl_object_init();
obj = efl_add(SIMPLE_CLASS, NULL);
fail_unless(obj);
coro = eina_coro_new(func, obj, EINA_CORO_STACK_SIZE_DEFAULT);
fail_unless(coro);
fail_if(eina_coro_run(&coro, &result, NULL)); // doesn't yield, so should finish
ck_assert_ptr_eq(result, RETVAL);
ck_assert_int_eq(efl_ref_get(obj), 2);
efl_unref(obj);
efl_unref(obj);
efl_object_shutdown();
}
END_TEST
void eo_test_coro(TCase *tc)
{
tcase_add_test(tc, eo_coro);
}