efl/src/lib/eina/eina_promise.c

1561 lines
44 KiB
C

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "eina_private.h"
#include "eina_lock.h"
#include "eina_promise.h"
#include "eina_mempool.h"
#include "eina_promise_private.h"
#include "eina_internal.h"
#include "eina_iterator.h"
#include <errno.h>
#include <stdarg.h>
#include <assert.h>
#define EINA_FUTURE_DISPATCHED ((Eina_Future_Cb)(0x01))
#define EFL_MEMPOOL_CHECK_RETURN(_type, _mp, _p) \
if (!eina_mempool_from((_mp), (_p))) \
{ \
ERR("The %s %p is not alive at mempool %p", (_type), (_p), (_mp)); \
return; \
}
#define EFL_MEMPOOL_CHECK_RETURN_VAL(_type, _mp, _p, _val) \
if (!eina_mempool_from((_mp), (_p))) \
{ \
ERR("The %s %p is not alive at mempool %p", (_type),(_p), (_mp)); \
return (_val); \
}
#define EFL_MEMPOOL_CHECK_GOTO(_type, _mp, _p, _goto) \
if (!eina_mempool_from((_mp), (_p))) \
{ \
ERR("The %s %p is not alive at mempool %p", (_type), (_p), (_mp)); \
goto _goto; \
}
#define EINA_PROMISE_CHECK_RETURN(_p) \
do { \
EINA_SAFETY_ON_NULL_RETURN((_p)); \
EFL_MEMPOOL_CHECK_RETURN("promise", _promise_mp, (_p)); \
} while (0);
#define EINA_PROMISE_CHECK_RETURN_VAL(_p, _val) \
do { \
EINA_SAFETY_ON_NULL_RETURN_VAL((_p), (_val)); \
EFL_MEMPOOL_CHECK_RETURN_VAL("promise", _promise_mp, (_p), (_val)); \
} while (0);
#define EINA_PROMISE_CHECK_GOTO(_p, _goto) \
do { \
EINA_SAFETY_ON_NULL_GOTO((_p), _goto); \
EFL_MEMPOOL_CHECK_GOTO("promise", _promise_mp, (_p), _goto); \
} while (0);
#define EINA_FUTURE_CHECK_GOTO(_p, _goto) \
do { \
EINA_SAFETY_ON_NULL_GOTO((_p), _goto); \
EFL_MEMPOOL_CHECK_GOTO("future", _future_mp, (_p), _goto); \
} while (0);
#define EINA_FUTURE_CHECK_RETURN(_p) \
do { \
EINA_SAFETY_ON_NULL_RETURN((_p)); \
EFL_MEMPOOL_CHECK_RETURN("future", _future_mp, (_p)); \
if (_p->cb == EINA_FUTURE_DISPATCHED) \
{ \
ERR("Future %p already dispatched", _p); \
return; \
} \
} while (0);
#define EINA_FUTURE_CHECK_RETURN_VAL(_p, _val) \
do { \
EINA_SAFETY_ON_NULL_RETURN_VAL((_p), (_val)); \
EFL_MEMPOOL_CHECK_RETURN_VAL("future", _future_mp, (_p), (_val)); \
if (_p->cb == EINA_FUTURE_DISPATCHED) \
{ \
ERR("Future %p already dispatched", _p); \
return (_val); \
} \
} while (0);
#undef ERR
#define ERR(...) EINA_LOG_DOM_ERR(_promise_log_dom, __VA_ARGS__)
#undef DBG
#define DBG(...) EINA_LOG_DOM_DBG(_promise_log_dom, __VA_ARGS__)
#undef INF
#define INF(...) EINA_LOG_DOM_INFO(_promise_log_dom, __VA_ARGS__)
#undef WRN
#define WRN(...) EINA_LOG_DOM_WARN(_promise_log_dom, __VA_ARGS__)
#undef CRI
#define CRI(...) EINA_LOG_DOM_CRIT(_promise_log_dom, __VA_ARGS__)
#define _eina_promise_value_dbg(_msg, _p, _v) __eina_promise_value_dbg(_msg, _p, _v, __LINE__, __func__)
struct _Eina_Promise {
Eina_Future *future;
Eina_Future_Scheduler *scheduler;
Eina_Promise_Cancel_Cb cancel;
const void *data;
};
struct _Eina_Future {
Eina_Promise *promise;
Eina_Future *next;
Eina_Future *prev;
Eina_Future_Cb cb;
const void *data;
Eina_Future **storage;
Eina_Future_Schedule_Entry *scheduled_entry;
};
static Eina_Mempool *_promise_mp = NULL;
static Eina_Mempool *_future_mp = NULL;
static Eina_Lock _pending_futures_lock;
static Eina_List *_pending_futures = NULL;
static int _promise_log_dom = -1;
static void _eina_promise_cancel(Eina_Promise *p);
static Eina_Value_Struct_Member RACE_STRUCT_MEMBERS[] = {
EINA_VALUE_STRUCT_MEMBER(NULL, Eina_Future_Race_Result, value),
EINA_VALUE_STRUCT_MEMBER(NULL, Eina_Future_Race_Result, index),
EINA_VALUE_STRUCT_MEMBER_SENTINEL
};
static const Eina_Value_Struct_Desc RACE_STRUCT_DESC = {
.version = EINA_VALUE_STRUCT_DESC_VERSION,
.ops = NULL,
.members = RACE_STRUCT_MEMBERS,
.member_count = 2,
.size = sizeof(Eina_Future_Race_Result)
};
/** @cond local */
EAPI const Eina_Value_Struct_Desc *EINA_PROMISE_RACE_STRUCT_DESC = &RACE_STRUCT_DESC;
/** @endcond */
static inline void
__eina_promise_value_dbg(const char *msg,
const Eina_Promise *p,
const Eina_Value v,
int line,
const char *fname)
{
if (EINA_UNLIKELY(eina_log_domain_level_check(_promise_log_dom,
EINA_LOG_LEVEL_DBG)))
{
if (!v.type)
{
eina_log_print(_promise_log_dom, EINA_LOG_LEVEL_DBG,
__FILE__, fname, line, "%s: %p with no value",
msg, p);
}
else
{
char *str = eina_value_to_string(&v);
eina_log_print(_promise_log_dom, EINA_LOG_LEVEL_DBG,
__FILE__, fname, line,
"%s: %p - Value Type: %s Content: %s", msg, p,
eina_value_type_name_get(eina_value_type_get(&v)),
str);
free(str);
}
}
}
static Eina_Bool
_promise_setup(const Eina_Value_Type *type EINA_UNUSED, void *mem)
{
Eina_Promise **tmem = mem;
*tmem = NULL;
return EINA_TRUE;
}
static Eina_Bool
_promise_flush(const Eina_Value_Type *type EINA_UNUSED, void *mem)
{
Eina_Promise **tmem = mem;
if (*tmem)
{
_eina_promise_cancel(*tmem);
*tmem = NULL;
}
return EINA_TRUE;
}
static void
_promise_replace(Eina_Promise **dst, Eina_Promise * const *src)
{
if (*src == *dst) return;
if (*dst) _eina_promise_cancel(*dst);
*dst = *src;
}
static Eina_Bool
_promise_vset(const Eina_Value_Type *type EINA_UNUSED, void *mem, va_list args)
{
Eina_Promise **dst = mem;
Eina_Promise **src = va_arg(args, Eina_Promise **);
_promise_replace(dst, src);
return EINA_TRUE;
}
static Eina_Bool
_promise_pset(const Eina_Value_Type *type EINA_UNUSED,
void *mem, const void *ptr)
{
Eina_Promise **dst = mem;
Eina_Promise * const *src = ptr;
_promise_replace(dst, src);
return EINA_TRUE;
}
static Eina_Bool
_promise_pget(const Eina_Value_Type *type EINA_UNUSED,
const void *mem, void *ptr)
{
Eina_Promise * const *src = mem;
Eina_Promise **dst = ptr;
*dst = *src;
return EINA_TRUE;
}
static Eina_Bool
_promise_convert_to(const Eina_Value_Type *type EINA_UNUSED, const Eina_Value_Type *convert, const void *type_mem, void *convert_mem)
{
Eina_Promise * const *p = type_mem;
if (convert == EINA_VALUE_TYPE_STRINGSHARE ||
convert == EINA_VALUE_TYPE_STRING)
{
const char *other_mem;
char buf[128];
snprintf(buf, sizeof(buf), "Promise %p (cancel: %p, data: %p)",
*p, (*p)->cancel, (*p)->data);
other_mem = buf;
return eina_value_type_pset(convert, convert_mem, &other_mem);
}
return EINA_FALSE;
}
const Eina_Value_Type EINA_VALUE_TYPE_PROMISE = {
.version = EINA_VALUE_TYPE_VERSION,
.value_size = sizeof(Eina_Promise *),
.name = "Eina_Promise",
.setup = _promise_setup,
.flush = _promise_flush,
.copy = NULL,
.compare = NULL,
.convert_to = _promise_convert_to,
.convert_from = NULL,
.vset = _promise_vset,
.pset = _promise_pset,
.pget = _promise_pget
};
static Eina_Promise *
_eina_value_promise_steal(Eina_Value *value)
{
Eina_Promise **p, *r;
/*
Do not use eina_value_flush()/eina_value_pset() in here,
otherwise it would cancel the promise!
*/
p = eina_value_memory_get(value);
r = *p;
*p = NULL;
return r;
}
static Eina_Future *
_eina_future_free(Eina_Future *f)
{
DBG("Free future %p", f);
Eina_Future *next = f->next;
Eina_Future *prev = f->prev;
if (next) next->prev = NULL;
if (prev) prev->next = NULL;
eina_mempool_free(_future_mp, f);
return next;
}
static void
_eina_promise_unlink(Eina_Promise *p)
{
if (p->future)
{
DBG("Unliking promise %p and future %p", p, p->future);
p->future->promise = NULL;
p->future = NULL;
}
}
static void
_eina_promise_link(Eina_Promise *p, Eina_Future *f)
{
assert(f != NULL);
if (p) p->future = f;
f->promise = p;
DBG("Linking future %p with promise %p", f, p);
}
static void
_eina_promise_cancel(Eina_Promise *p)
{
DBG("Cancelling promise: %p, data: %p, future: %p", p, p->data, p->future);
_eina_promise_unlink(p);
p->cancel((void *)p->data, p);
eina_mempool_free(_promise_mp, p);
}
static void
_eina_promise_value_steal_and_link(Eina_Future_Scheduler *scheduler,
Eina_Value value,
Eina_Future *f)
{
Eina_Promise *p = _eina_value_promise_steal(&value);
DBG("Promise %p stolen from value", p);
eina_value_flush(&value);
// In the case of continue promise, define a scheduler when steal&link
if (!p->scheduler) p->scheduler = scheduler;
if (f) _eina_promise_link(p, f);
else _eina_promise_unlink(p);
}
static Eina_Value
_eina_future_cb_dispatch(Eina_Future *f, const Eina_Value value)
{
Eina_Future_Cb cb = f->cb;
// Cleaning up prev and past to avoid recursion
if (f->next) f->next->prev = NULL;
if (f->prev) f->prev->next = NULL;
f->cb = EINA_FUTURE_DISPATCHED;
if (f->storage) *f->storage = NULL;
if (EINA_UNLIKELY(eina_log_domain_level_check(_promise_log_dom, EINA_LOG_LEVEL_DBG)))
{
if (!value.type) DBG("Distach cb: %p, data: %p with no value", cb, f->data);
else
{
char *str = eina_value_to_string(&value);
DBG("Dispatch cb: %p, data: %p, value type: %s content: '%s'",
cb, f->data,
eina_value_type_name_get(eina_value_type_get(&value)), str);
free(str);
}
}
return cb((void *)f->data, value, f);
}
static Eina_Value
_eina_future_dispatch_internal(Eina_Future **f,
const Eina_Value value)
{
Eina_Value next_value = EINA_VALUE_EMPTY;
assert(value.type != &EINA_VALUE_TYPE_PROMISE);
while ((*f) && (!(*f)->cb)) *f = _eina_future_free(*f);
if (!*f)
{
_eina_promise_value_dbg("No future to deliver value", NULL, value);
return value;
}
if ((*f)->cb == EINA_FUTURE_DISPATCHED) return value;
next_value = _eina_future_cb_dispatch(*f, value);
*f = _eina_future_free(*f);
return next_value;
}
static Eina_Bool
_eina_value_is(const Eina_Value v1, const Eina_Value v2)
{
if (v1.type != v2.type) return EINA_FALSE;
//Both types are NULL at this point... so they are equal...
if (!v1.type) return EINA_TRUE;
return !memcmp(eina_value_memory_get(&v1),
eina_value_memory_get(&v2),
v1.type->value_size);
}
static void
_eina_future_dispatch(Eina_Future_Scheduler *scheduler, Eina_Future *f, Eina_Value value)
{
Eina_Value next_value = _eina_future_dispatch_internal(&f, value);
if (!_eina_value_is(next_value, value)) eina_value_flush(&value);
if (!f)
{
if (next_value.type == &EINA_VALUE_TYPE_PROMISE)
{
DBG("There are no more futures, but next_value is a promise setting p->future to NULL.");
_eina_promise_value_steal_and_link(scheduler, next_value, NULL);
}
else eina_value_flush(&next_value);
return;
}
// Break early if finding a cb that is already dispatching
if (f->cb == EINA_FUTURE_DISPATCHED)
{
eina_value_flush(&next_value);
return;
}
if (next_value.type == &EINA_VALUE_TYPE_PROMISE)
{
if (EINA_UNLIKELY(eina_log_domain_level_check(_promise_log_dom, EINA_LOG_LEVEL_DBG)))
{
Eina_Promise *p = NULL;
eina_value_pget(&next_value, &p);
DBG("Future %p will wait for a new promise %p", f, p);
}
_eina_promise_value_steal_and_link(scheduler, next_value, f);
}
else _eina_future_dispatch(scheduler, f, next_value);
}
static void
_scheduled_entry_cb(Eina_Future *f, Eina_Value value)
{
EINA_SAFETY_ON_NULL_RETURN(f->scheduled_entry);
// This function is called by the scheduler, so it has to be defined
Eina_Future_Scheduler *scheduler = f->scheduled_entry->scheduler;
eina_lock_take(&_pending_futures_lock);
_pending_futures = eina_list_remove(_pending_futures, f);
eina_lock_release(&_pending_futures_lock);
f->scheduled_entry = NULL;
_eina_future_dispatch(scheduler, f, value);
}
void
eina_future_schedule_entry_recall(Eina_Future_Schedule_Entry *entry)
{
entry->scheduler->recall(entry);
}
static void
_eina_future_cancel(Eina_Future *f, int err)
{
Eina_Value value = EINA_VALUE_EMPTY;
DBG("Cancelling future %p, cb: %p data: %p with error: %d - msg: '%s'",
f, f->cb, f->data, err, eina_error_msg_get(err));
for (; f->prev != NULL; f = f->prev)
{
assert(f->promise == NULL); /* intermediate futures shouldn't have a promise */
assert(f->scheduled_entry == NULL); /* intermediate futures shouldn't have pending dispatch */
}
if (f->scheduled_entry)
{
eina_future_schedule_entry_recall(f->scheduled_entry);
f->scheduled_entry = NULL;
eina_lock_take(&_pending_futures_lock);
_pending_futures = eina_list_remove(_pending_futures, f);
eina_lock_release(&_pending_futures_lock);
}
if (f->promise)
{
_eina_promise_cancel(f->promise);
f->promise = NULL;
}
eina_value_setup(&value, EINA_VALUE_TYPE_ERROR);
eina_value_set(&value, err);
while (f)
{
// Stop on partially dispatched future
if (f->cb == EINA_FUTURE_DISPATCHED) break;
if (f->cb)
{
Eina_Value r = _eina_future_cb_dispatch(f, value);
if (!_eina_value_is(value, r)) eina_value_flush(&r);
}
f = _eina_future_free(f);
}
eina_value_flush(&value);
}
static void
_eina_future_schedule(Eina_Promise *p,
Eina_Future *f,
Eina_Value value)
{
if (!p->scheduler)
{
ERR("Trying to resolve a continue promise during a future callback. Directly return Eina_Value instead.");
goto err;
}
f->scheduled_entry = p->scheduler->schedule(p->scheduler,
_scheduled_entry_cb,
f, value);
EINA_SAFETY_ON_NULL_GOTO(f->scheduled_entry, err);
assert(f->scheduled_entry->scheduler != NULL);
eina_lock_take(&_pending_futures_lock);
_pending_futures = eina_list_append(_pending_futures, f);
eina_lock_release(&_pending_futures_lock);
DBG("The promise %p schedule the future %p with cb: %p and data: %p",
p, f, f->cb, f->data);
return;
err:
_eina_future_cancel(p->future, ENOMEM);
eina_value_flush(&value);
}
static void
_eina_promise_deliver(Eina_Promise *p,
Eina_Value value)
{
if (p->future)
{
Eina_Future *f = p->future;
_eina_promise_unlink(p);
if (value.type == &EINA_VALUE_TYPE_PROMISE)
_eina_promise_value_steal_and_link(p->scheduler, value, f);
else _eina_future_schedule(p, f, value);
}
else
{
DBG("Promise %p has no future", p);
eina_value_flush(&value);
}
eina_mempool_free(_promise_mp, p);
}
Eina_Bool
eina_promise_init(void)
{
const char *choice = getenv("EINA_MEMPOOL");
if ((!choice) || (!choice[0])) choice = "chained_mempool";
RACE_STRUCT_MEMBERS[0].type = EINA_VALUE_TYPE_VALUE;
RACE_STRUCT_MEMBERS[1].type = EINA_VALUE_TYPE_UINT;
_promise_log_dom = eina_log_domain_register("eina_promise", EINA_COLOR_CYAN);
if (_promise_log_dom < 0)
{
EINA_LOG_ERR("Could not creae the Eina_Promise domain");
return EINA_FALSE;
}
//FIXME: Is 512 too high?
_promise_mp = eina_mempool_add(choice, "Eina_Promise",
NULL, sizeof(Eina_Promise), 512);
EINA_SAFETY_ON_NULL_GOTO(_promise_mp, err_promise);
_future_mp = eina_mempool_add(choice, "Eina_Future",
NULL, sizeof(Eina_Future), 512);
EINA_SAFETY_ON_NULL_GOTO(_future_mp, err_future);
eina_lock_recursive_new(&_pending_futures_lock);
return EINA_TRUE;
err_future:
eina_mempool_del(_promise_mp);
_promise_mp = NULL;
err_promise:
eina_log_domain_unregister(_promise_log_dom);
_promise_log_dom = -1;
return EINA_FALSE;
}
EAPI void
__eina_promise_cancel_all(void)
{
eina_lock_take(&_pending_futures_lock);
while (_pending_futures)
_eina_future_cancel(_pending_futures->data, ECANCELED);
eina_lock_release(&_pending_futures_lock);
}
EAPI void
__eina_promise_cancel_data(void *data)
{
Eina_List *del = NULL, *l;
Eina_Future *f;
eina_lock_take(&_pending_futures_lock);
EINA_LIST_FOREACH(_pending_futures, l, f)
{
if (f->data == data)
{
del = eina_list_append(del, f);
}
}
EINA_LIST_FREE(del, f)
{
_eina_future_cancel(f, ECANCELED);
}
eina_lock_release(&_pending_futures_lock);
}
Eina_Bool
eina_promise_shutdown(void)
{
__eina_promise_cancel_all();
eina_lock_free(&_pending_futures_lock);
eina_mempool_del(_future_mp);
eina_mempool_del(_promise_mp);
eina_log_domain_unregister(_promise_log_dom);
_promise_log_dom = -1;
_promise_mp = NULL;
_future_mp = NULL;
return EINA_TRUE;
}
EAPI Eina_Value
eina_promise_as_value(Eina_Promise *p)
{
Eina_Value v = EINA_VALUE_EMPTY;
Eina_Bool r;
EINA_PROMISE_CHECK_RETURN_VAL(p, v);
r = eina_value_setup(&v, &EINA_VALUE_TYPE_PROMISE);
EINA_SAFETY_ON_FALSE_GOTO(r, err_setup);
r = eina_value_pset(&v, &p);
EINA_SAFETY_ON_FALSE_GOTO(r, err_pset);
DBG("Created value from promise %p", p);
return v;
err_pset:
eina_value_flush(&v);
memset(&v, 0, sizeof(Eina_Value));
err_setup:
if (p->future) _eina_future_cancel(p->future, ENOMEM);
else _eina_promise_cancel(p);
return v;
}
static void
_eina_promise_clean_dispatch(Eina_Promise *p, Eina_Value v)
{
Eina_Future *f = p->future;
if (f)
{
_eina_promise_value_dbg("Clean contenxt - Resolving promise", p, v);
_eina_promise_unlink(p);
// This function is called on a promise created with a scheduler, not a continue one.
_eina_future_dispatch(p->scheduler, f, v);
}
else
{
// Nobody is going to flush this value if we don't
eina_value_flush(&v);
}
eina_mempool_free(_promise_mp, p);
}
static Eina_Value
_future_proxy(void *data, const Eina_Value v,
const Eina_Future *dead_future EINA_UNUSED)
{
Eina_Value copy = EINA_VALUE_EMPTY;
if (eina_value_type_get(&v) == EINA_VALUE_TYPE_ERROR) return v;
//We're in a safe context (from mainloop), so we can avoid scheduling a new dispatch
if (!v.type || !memcmp(&v, &copy, sizeof (Eina_Value))) copy = v;
else if (!eina_value_copy(&v, &copy))
{
ERR("Value cannot be copied - unusable with Eina_Future: %p (%s)", v.type, v.type->name);
eina_value_setup(&copy, EINA_VALUE_TYPE_ERROR);
eina_value_set(&copy, ENOTSUP);
}
_eina_promise_clean_dispatch(data, copy);
return v;
}
static void
_dummy_cancel(void *data EINA_UNUSED, const Eina_Promise *dead_ptr EINA_UNUSED)
{
}
static Eina_Future_Scheduler *
_scheduler_get(Eina_Future *f)
{
do
{
if (f->promise) return f->promise->scheduler;
else if (f->scheduled_entry) return f->scheduled_entry->scheduler;
f = f->prev;
}
while (f);
assert(EINA_FALSE && "no scheduler for future!");
return NULL;
}
EAPI Eina_Value
eina_future_as_value(Eina_Future *f)
{
Eina_Value v = EINA_VALUE_EMPTY;
Eina_Promise *p;
Eina_Future *r_future;
EINA_FUTURE_CHECK_RETURN_VAL(f, v);
p = eina_promise_new(_scheduler_get(f), _dummy_cancel, NULL);
EINA_SAFETY_ON_NULL_GOTO(p, err_promise);
r_future = eina_future_then(f, _future_proxy, p, NULL);
//If eina_future_then() fails f will be cancelled
EINA_SAFETY_ON_NULL_GOTO(r_future, err_future);
v = eina_promise_as_value(p);
if (v.type == &EINA_VALUE_TYPE_PROMISE)
{
DBG("Creating future proxy for future: %p - promise %p", f, p);
return v;
}
//The promise was freed by eina_promise_as_value()
ERR("Could not create a Eina_Value for future %p", f);
//Futures will be unlinked
_eina_future_free(r_future);
_eina_future_cancel(f, ENOMEM);
return v;
err_future:
/*
Do not cancel the _eina_future_cancel(f,..) here, it
was already canceled by eina_future_then().
*/
_eina_promise_cancel(p);
return v;
err_promise:
_eina_future_cancel(f, ENOMEM);
return v;
}
EAPI Eina_Promise *
eina_promise_new(Eina_Future_Scheduler *scheduler,
Eina_Promise_Cancel_Cb cancel_cb, const void *data)
{
EINA_SAFETY_ON_NULL_RETURN_VAL(cancel_cb, NULL);
EINA_SAFETY_ON_NULL_RETURN_VAL(scheduler, NULL);
EINA_SAFETY_ON_NULL_RETURN_VAL(scheduler->schedule, NULL);
EINA_SAFETY_ON_NULL_RETURN_VAL(scheduler->recall, NULL);
Eina_Promise *p = eina_mempool_calloc(_promise_mp, sizeof(Eina_Promise));
EINA_SAFETY_ON_NULL_RETURN_VAL(p, NULL);
p->cancel = cancel_cb;
p->data = data;
p->scheduler = scheduler;
DBG("Creating new promise - Promise:%p, cb: %p, data:%p", p,
p->cancel, p->data);
return p;
}
EAPI Eina_Promise *
eina_promise_continue_new(const Eina_Future *dead_future,
Eina_Promise_Cancel_Cb cancel_cb, const void *data)
{
EINA_SAFETY_ON_NULL_RETURN_VAL(cancel_cb, NULL);
EINA_SAFETY_ON_NULL_RETURN_VAL(dead_future, NULL);
EINA_SAFETY_ON_TRUE_RETURN_VAL(dead_future->scheduled_entry != NULL, NULL);
EINA_SAFETY_ON_TRUE_RETURN_VAL(dead_future->promise != NULL, NULL);
Eina_Promise *p = eina_mempool_calloc(_promise_mp, sizeof(Eina_Promise));
EINA_SAFETY_ON_NULL_RETURN_VAL(p, NULL);
p->cancel = cancel_cb;
p->data = data;
p->scheduler = NULL;
DBG("Creating continuing new promise - Promise:%p, cb: %p, data:%p", p,
p->cancel, p->data);
return p;
}
EAPI void
eina_future_cancel(Eina_Future *f)
{
EINA_FUTURE_CHECK_RETURN(f);
_eina_future_cancel(f, ECANCELED);
}
EAPI void
eina_promise_resolve(Eina_Promise *p, Eina_Value value)
{
EINA_PROMISE_CHECK_GOTO(p, err);
_eina_promise_value_dbg("Resolve promise", p, value);
_eina_promise_deliver(p, value);
return;
err:
eina_value_flush(&value);
}
EAPI void
eina_promise_reject(Eina_Promise *p, Eina_Error err)
{
Eina_Value value;
Eina_Bool r;
EINA_PROMISE_CHECK_RETURN(p);
r = eina_value_setup(&value, EINA_VALUE_TYPE_ERROR);
EINA_SAFETY_ON_FALSE_GOTO(r, err_setup);
r = eina_value_set(&value, err);
EINA_SAFETY_ON_FALSE_GOTO(r, err_set);
DBG("Reject promise %p - Error msg: '%s' - Error code: %d", p,
eina_error_msg_get(err), err);
_eina_promise_deliver(p, value);
return;
err_set:
eina_value_flush(&value);
err_setup:
if (p->future) _eina_future_cancel(p->future, ENOMEM);
else _eina_promise_cancel(p);
}
static void
_fake_future_dispatch(const Eina_Future_Desc desc, int err)
{
/*
This function is used to dispatch the Eina_Future_Cb in case,
the future creation fails. By calling the Eina_Future_Cb
the user has a chance to free allocated resources.
*/
Eina_Value v, r;
if (desc.storage) *desc.storage = NULL;
if (!desc.cb) return;
eina_value_setup(&v, EINA_VALUE_TYPE_ERROR);
eina_value_set(&v, err);
//Since the future was not created the dead_ptr is NULL.
r = desc.cb((void *)desc.data, v, NULL);
eina_value_flush(&r);
eina_value_flush(&v);
}
static Eina_Future *
_eina_future_new(Eina_Promise *p, const Eina_Future_Desc desc)
{
Eina_Future *f;
f = eina_mempool_calloc(_future_mp, sizeof(Eina_Future));
EINA_SAFETY_ON_NULL_GOTO(f, err_future);
_eina_promise_link(p, f);
f->cb = desc.cb;
f->data = desc.data;
if (desc.storage)
{
*desc.storage = f;
f->storage = desc.storage;
}
DBG("Creating new future - Promise:%p, Future:%p, cb: %p, data: %p ",
p, f, f->cb, f->data);
return f;
err_future:
_fake_future_dispatch(desc, ENOMEM);
if (p) _eina_promise_cancel(p);
return NULL;
}
EAPI Eina_Future *
eina_future_new(Eina_Promise *p)
{
static const Eina_Future_Desc desc = {
.cb = NULL,
.data = NULL
};
EINA_PROMISE_CHECK_RETURN_VAL(p, NULL);
EINA_SAFETY_ON_TRUE_GOTO(p->future != NULL, err_has_future);
return _eina_future_new(p, desc);
err_has_future:
//_eina_future_cancel() will also cancel the promise
_eina_future_cancel(p->future, EINVAL);
return NULL;
}
static Eina_Future *
_eina_future_then(Eina_Future *prev, const Eina_Future_Desc desc)
{
Eina_Future *next = _eina_future_new(NULL, desc);
EINA_SAFETY_ON_NULL_GOTO(next, err_next);
next->prev = prev;
prev->next = next;
DBG("Linking futures - Prev:%p Next:%p", prev, next);
return next;
err_next:
//_fake_future_dispatch() already called by _eina_future_new()
_eina_future_cancel(prev, ENOMEM);
return NULL;
}
EAPI Eina_Future *
eina_future_resolved(Eina_Future_Scheduler *scheduler, Eina_Value value)
{
Eina_Promise *p;
Eina_Future *f;
EINA_SAFETY_ON_NULL_GOTO(scheduler, error);
p = eina_promise_new(scheduler, _dummy_cancel, NULL);
EINA_SAFETY_ON_NULL_GOTO(p, error);
f = eina_future_new(p);
EINA_SAFETY_ON_NULL_GOTO(f, error);
eina_promise_resolve(p, value);
return f;
error:
eina_value_flush(&value);
return NULL;
}
EAPI Eina_Future *
eina_future_rejected(Eina_Future_Scheduler *scheduler, Eina_Error err)
{
Eina_Promise *p;
Eina_Future *f;
EINA_SAFETY_ON_NULL_GOTO(scheduler, error);
p = eina_promise_new(scheduler, _dummy_cancel, NULL);
EINA_SAFETY_ON_NULL_GOTO(p, error);
f = eina_future_new(p);
EINA_SAFETY_ON_NULL_GOTO(f, error);
eina_promise_reject(p, err);
return f;
error:
return NULL;
}
EAPI Eina_Future *
eina_future_then_from_desc(Eina_Future *prev, const Eina_Future_Desc desc)
{
EINA_FUTURE_CHECK_GOTO(prev, err_future);
EINA_SAFETY_ON_TRUE_GOTO(prev->next != NULL, err_next);
return _eina_future_then(prev, desc);
err_next:
_eina_future_cancel(prev->next, EINVAL);
err_future:
_fake_future_dispatch(desc, EINVAL);
return NULL;
}
EAPI Eina_Future *
eina_future_chain_array(Eina_Future *prev, const Eina_Future_Desc descs[])
{
Eina_Future *f = prev;
ssize_t i = -1;
int err = EINVAL;
EINA_FUTURE_CHECK_GOTO(prev, err_prev);
EINA_SAFETY_ON_TRUE_GOTO(prev->next != NULL, err_next);
err = ENOMEM;
for (i = 0; descs[i].cb; i++)
{
f = _eina_future_then(f, descs[i]);
/*
If _eina_future_then() fails the whole chain will be cancelled by it.
All we need to do is free the remaining descs..
*/
EINA_SAFETY_ON_NULL_GOTO(f, err_prev);
}
return f;
err_next:
_eina_future_cancel(f, err);
err_prev:
/*
If i > 0 we'll start to dispatch fake futures
at i + 1, since the descs[i] was already freed
by _eina_future_then()
*/
for (i = i + 1; descs[i].cb; i++)
_fake_future_dispatch(descs[i], err);
return NULL;
}
EAPI Eina_Future *
eina_future_chain_easy_array(Eina_Future *prev, const Eina_Future_Cb_Easy_Desc descs[])
{
size_t i = -1;
Eina_Future *f = prev;
int err = EINVAL;
EINA_FUTURE_CHECK_GOTO(prev, err_prev);
EINA_SAFETY_ON_TRUE_GOTO(prev->next != NULL, err_next);
err = ENOMEM;
for (i = 0; descs[i].success || descs[i].error || descs[i].free || descs[i].success_type; i++)
{
Eina_Future_Desc fdesc = eina_future_cb_easy_from_desc(descs[i]);
f = _eina_future_then(f, fdesc);
EINA_SAFETY_ON_NULL_GOTO(f, err_prev);
}
return f;
err_next:
_eina_future_cancel(f, err);
err_prev:
/*
If i > 0 we'll start to dispatch fake futures
at i + 1, since the descs[i] was already freed
by _eina_future_then()
*/
for (i = i + 1; descs[i].error || descs[i].free; i++)
{
if (descs[i].error)
{
Eina_Value v = descs[i].error((void *)descs[i].data, err);
eina_value_flush(&v);
}
if (descs[i].free) descs[i].free((void *)descs[i].data, NULL);
}
return NULL;
}
static Eina_Value
_eina_future_cb_console(void *data,
const Eina_Value value,
const Eina_Future *dead_future EINA_UNUSED)
{
Eina_Future_Cb_Console_Desc *c = data;
const char *prefix = c ? c->prefix : NULL;
const char *suffix = c ? c->suffix : NULL;
const char *content = "no value";
char *str = NULL;
if (value.type)
{
str = eina_value_to_string(&value);
content = str;
}
if (!prefix) prefix = "";
if (!suffix) suffix = "\n";
printf("%s%s%s", prefix, content, suffix);
free(str);
if (c) {
free((void *)c->prefix);
free((void *)c->suffix);
free(c);
}
return value;
}
EAPI Eina_Future_Desc
eina_future_cb_console_from_desc(const Eina_Future_Cb_Console_Desc desc)
{
Eina_Future_Cb_Console_Desc *c;
Eina_Future_Desc fdesc = {
.cb = _eina_future_cb_console,
.data = NULL
};
if (desc.prefix || desc.suffix) {
fdesc.data = c = calloc(1, sizeof(Eina_Future_Cb_Console_Desc));
EINA_SAFETY_ON_NULL_GOTO(c, exit);
c->prefix = desc.prefix ? strdup(desc.prefix) : NULL;
c->suffix = desc.suffix ? strdup(desc.suffix) : NULL;
}
exit:
return fdesc;
}
static Eina_Value
_eina_future_cb_convert_to(void *data, const Eina_Value src,
const Eina_Future *dead_future EINA_UNUSED)
{
const Eina_Value_Type *type = data;
Eina_Value dst = EINA_VALUE_EMPTY;
if (!type)
{
DBG("Trying to convert type '%s' to a NULL type - Ignoring (pass thru)",
src.type->name);
return src; // pass thru
}
if (!eina_value_setup(&dst, type))
{
ERR("Could not setup an Eina_Value with type named: '%s'", type->name);
eina_value_setup(&dst, EINA_VALUE_TYPE_ERROR);
eina_value_set(&dst, ENOMEM);
}
else if (src.type && !eina_value_convert(&src, &dst))
{
//Clean the type
ERR("Could not convert the Eina_Value type %s to %s",
src.type->name, dst.type->name);
eina_value_flush(&dst);
eina_value_setup(&dst, EINA_VALUE_TYPE_ERROR);
eina_value_set(&dst, ENOTSUP);
}// otherwise leave initial value for empty
return dst;
}
EAPI Eina_Future_Desc
eina_future_cb_convert_to(const Eina_Value_Type *type)
{
return (Eina_Future_Desc){.cb = _eina_future_cb_convert_to, .data = type};
}
static Eina_Value
_eina_future_cb_easy(void *data, const Eina_Value value,
const Eina_Future *dead_future)
{
Eina_Future_Cb_Easy_Desc *d = data;
Eina_Value ret = EINA_VALUE_EMPTY;
if (!d)
{
if (eina_value_setup(&ret, EINA_VALUE_TYPE_ERROR)) eina_value_set(&ret, ENOMEM);
return ret;
}
EASY_FUTURE_DISPATCH(ret, value, dead_future, d, (void*)d->data);
free(d);
return ret;
}
EAPI Eina_Future_Desc
eina_future_cb_easy_from_desc(const Eina_Future_Cb_Easy_Desc desc)
{
Eina_Future_Cb_Easy_Desc *d = calloc(1, sizeof(Eina_Future_Cb_Easy_Desc));
EINA_SAFETY_ON_NULL_GOTO(d, end);
*d = desc;
end:
return (Eina_Future_Desc){ .cb = _eina_future_cb_easy, .data = d };
}
typedef struct _Base_Ctx {
Eina_Promise *promise;
Eina_Future **futures;
unsigned int futures_len;
} Base_Ctx;
typedef struct _All_Promise_Ctx {
Base_Ctx base;
Eina_Value values;
unsigned int processed;
} All_Promise_Ctx;
typedef struct _Race_Promise_Ctx {
Base_Ctx base;
Eina_Bool dispatching;
} Race_Promise_Ctx;
static void
_base_ctx_clean(Base_Ctx *ctx)
{
unsigned int i;
for (i = 0; i < ctx->futures_len; i++)
if (ctx->futures[i]) _eina_future_cancel(ctx->futures[i], ECANCELED);
free(ctx->futures);
}
static void
_all_promise_ctx_free(All_Promise_Ctx *ctx)
{
_base_ctx_clean(&ctx->base);
eina_value_flush(&ctx->values);
free(ctx);
}
static void
_all_promise_cancel(void *data, const Eina_Promise *dead EINA_UNUSED)
{
_all_promise_ctx_free(data);
}
static void
_race_promise_ctx_free(Race_Promise_Ctx *ctx)
{
_base_ctx_clean(&ctx->base);
free(ctx);
}
static void
_race_promise_cancel(void *data, const Eina_Promise *dead EINA_UNUSED)
{
_race_promise_ctx_free(data);
}
static Eina_Bool
_future_unset(Base_Ctx *ctx, unsigned int *pos, const Eina_Future *dead_ptr)
{
unsigned int i;
for (i = 0; i < ctx->futures_len; i++)
{
if (ctx->futures[i] == dead_ptr)
{
ctx->futures[i] = NULL;
*pos = i;
return EINA_TRUE;
}
}
return EINA_FALSE;
}
static Eina_Value
_race_then_cb(void *data, const Eina_Value v,
const Eina_Future *dead_ptr)
{
Race_Promise_Ctx *ctx = data;
Eina_Promise *p = ctx->base.promise;
Eina_Bool found, r;
Eina_Value result;
unsigned int i = 0;
//This is not allowed!
assert(v.type != &EINA_VALUE_TYPE_PROMISE);
found = _future_unset(&ctx->base, &i, dead_ptr);
assert(found); (void) found;
if (ctx->dispatching) return EINA_VALUE_EMPTY;
ctx->dispatching = EINA_TRUE;
//By freeing the race_ctx all the other futures will be cancelled.
_race_promise_ctx_free(ctx);
r = eina_value_struct_setup(&result, &RACE_STRUCT_DESC);
EINA_SAFETY_ON_FALSE_GOTO(r, err_setup);
r = eina_value_struct_set(&result, "value", v);
EINA_SAFETY_ON_FALSE_GOTO(r, err_set);
r = eina_value_struct_set(&result, "index", i);
EINA_SAFETY_ON_FALSE_GOTO(r, err_set);
//We're in a safe context (from mainloop), so we can avoid scheduling a new dispatch
_eina_promise_clean_dispatch(p, result);
return v;
err_set:
eina_value_flush(&result);
err_setup:
eina_value_setup(&result, EINA_VALUE_TYPE_ERROR);
eina_value_set(&result, ENOMEM);
_eina_promise_clean_dispatch(p, result);
return v;
}
static Eina_Value
_all_then_cb(void *data, const Eina_Value v,
const Eina_Future *dead_ptr)
{
All_Promise_Ctx *ctx = data;
unsigned int i = 0;
Eina_Bool found;
//This is not allowed!
assert(v.type != &EINA_VALUE_TYPE_PROMISE);
found = _future_unset(&ctx->base, &i, dead_ptr);
assert(found); (void) found;
ctx->processed++;
eina_value_array_set(&ctx->values, i, v);
if (ctx->processed == ctx->base.futures_len)
{
//We're in a safe context (from mainloop), so we can avoid scheduling a new dispatch
_eina_promise_clean_dispatch(ctx->base.promise, ctx->values);
ctx->values = EINA_VALUE_EMPTY; /* flushed in _eina_promise_clean_dispatch() */
_all_promise_ctx_free(ctx);
}
return v;
}
static void
_future_array_cancel(Eina_Future *array[])
{
size_t i;
for (i = 0; array[i]; i++) _eina_future_cancel(array[i], ENOMEM);
}
static Eina_Bool
promise_proxy_of_future_array_create(Eina_Future *array[],
Base_Ctx *ctx,
Eina_Promise_Cancel_Cb cancel_cb,
Eina_Future_Cb future_cb)
{
unsigned int i;
//Count how many futures...
for (i = 0; array[i] != EINA_FUTURE_SENTINEL; i++)
//NULL futures are not allowed.
EINA_SAFETY_ON_NULL_RETURN_VAL(array[i], EINA_FALSE);
EINA_SAFETY_ON_FALSE_RETURN_VAL(i > 0, EINA_FALSE);
ctx->promise = eina_promise_new(_scheduler_get(array[0]), cancel_cb, ctx);
EINA_SAFETY_ON_NULL_RETURN_VAL(ctx->promise, EINA_FALSE);
ctx->futures_len = i;
ctx->futures = calloc(ctx->futures_len, sizeof(Eina_Future *));
EINA_SAFETY_ON_NULL_GOTO(ctx->futures, err_futures);
for (i = 0; i < ctx->futures_len; i++)
{
ctx->futures[i] = eina_future_then(array[i], future_cb, ctx, NULL);
//Futures will be cancelled by the caller...
EINA_SAFETY_ON_NULL_GOTO(ctx->futures[i], err_then);
}
return EINA_TRUE;
err_then:
//This will also unlink the prev future...
while (i >= 1) _eina_future_free(ctx->futures[--i]);
free(ctx->futures);
ctx->futures = NULL;
err_futures:
ctx->futures_len = 0;
eina_mempool_free(_promise_mp, ctx->promise);
ctx->promise = NULL;
return EINA_FALSE;
}
EAPI Eina_Promise *
eina_promise_all_iterator(Eina_Iterator *it)
{
All_Promise_Ctx *ctx;
Eina_Future *f;
unsigned int i = 1;
Eina_Bool r;
EINA_SAFETY_ON_NULL_RETURN_VAL(it, NULL);
ctx = calloc(1, sizeof(All_Promise_Ctx));
EINA_SAFETY_ON_NULL_GOTO(ctx, err_ctx);
r = eina_value_array_setup(&ctx->values, EINA_VALUE_TYPE_VALUE, 0);
EINA_SAFETY_ON_FALSE_GOTO(r, err_array);
r = eina_iterator_next(it, (void**) &f);
EINA_SAFETY_ON_FALSE_GOTO(r, err_array);
ctx->base.promise = eina_promise_new(_scheduler_get(f), _all_promise_cancel, ctx);
EINA_SAFETY_ON_NULL_GOTO(ctx->base.promise, err_array);
ctx->base.futures_len = 1;
ctx->base.futures = calloc(1, sizeof (Eina_Future *));
EINA_SAFETY_ON_NULL_GOTO(ctx->base.futures, err_futures);
ctx->base.futures[0] = eina_future_then(f, _all_then_cb, ctx, NULL);
EINA_ITERATOR_FOREACH(it, f)
{
Eina_Future **tmp;
ctx->base.futures_len++;
tmp = realloc(ctx->base.futures, ctx->base.futures_len * sizeof (Eina_Future *));
EINA_SAFETY_ON_NULL_GOTO(tmp, err_futures);
ctx->base.futures = tmp;
ctx->base.futures[i] = eina_future_then(f, _all_then_cb, ctx, NULL);
EINA_SAFETY_ON_NULL_GOTO(ctx->base.futures[i++], err_futures);
}
for (i = 0; i < ctx->base.futures_len; i++)
{
Eina_Value v = { 0 };
//Stub values...
r = eina_value_setup(&v, EINA_VALUE_TYPE_INT);
EINA_SAFETY_ON_FALSE_GOTO(r, err_futures);
r = eina_value_array_append(&ctx->values, v);
eina_value_flush(&v);
EINA_SAFETY_ON_FALSE_GOTO(r, err_futures);
}
return ctx->base.promise;
err_futures:
while (i >= 1) _eina_future_free(ctx->base.futures[--i]);
free(ctx->base.futures);
ctx->base.futures = NULL;
eina_mempool_free(_promise_mp, ctx->base.promise);
eina_value_flush(&ctx->values);
err_array:
free(ctx);
err_ctx:
eina_iterator_free(it);
return NULL;
}
EAPI Eina_Promise *
eina_promise_all_array(Eina_Future *array[])
{
All_Promise_Ctx *ctx;
unsigned int i;
Eina_Bool r;
EINA_SAFETY_ON_NULL_RETURN_VAL(array, NULL);
ctx = calloc(1, sizeof(All_Promise_Ctx));
EINA_SAFETY_ON_NULL_GOTO(ctx, err_ctx);
r = eina_value_array_setup(&ctx->values, EINA_VALUE_TYPE_VALUE, 0);
EINA_SAFETY_ON_FALSE_GOTO(r, err_array);
r = promise_proxy_of_future_array_create(array, &ctx->base,
_all_promise_cancel, _all_then_cb);
EINA_SAFETY_ON_FALSE_GOTO(r, err_promise);
for (i = 0; i < ctx->base.futures_len; i++)
{
Eina_Value v = { 0 };
//Stub values...
r = eina_value_setup(&v, EINA_VALUE_TYPE_INT);
EINA_SAFETY_ON_FALSE_GOTO(r, err_stub);
r = eina_value_array_append(&ctx->values, v);
eina_value_flush(&v);
EINA_SAFETY_ON_FALSE_GOTO(r, err_stub);
}
return ctx->base.promise;
err_stub:
for (i = 0; i < ctx->base.futures_len; i++) _eina_future_free(ctx->base.futures[i]);
free(ctx->base.futures);
eina_mempool_free(_promise_mp, ctx->base.promise);
err_promise:
eina_value_flush(&ctx->values);
err_array:
free(ctx);
err_ctx:
_future_array_cancel(array);
return NULL;
}
EAPI Eina_Promise *
eina_promise_race_array(Eina_Future *array[])
{
Race_Promise_Ctx *ctx;
Eina_Bool r;
EINA_SAFETY_ON_NULL_RETURN_VAL(array, NULL);
ctx = calloc(1, sizeof(Race_Promise_Ctx));
EINA_SAFETY_ON_NULL_GOTO(ctx, err_ctx);
r = promise_proxy_of_future_array_create(array, &ctx->base,
_race_promise_cancel,
_race_then_cb);
EINA_SAFETY_ON_FALSE_GOTO(r, err_promise);
return ctx->base.promise;
err_promise:
free(ctx);
err_ctx:
_future_array_cancel(array);
return NULL;
}
static Eina_Value
_eina_future_cb_ignore_error(void *data, const Eina_Value value,
const Eina_Future *dead_future EINA_UNUSED)
{
Eina_Error expected_err = (Eina_Error)(intptr_t)data;
if (value.type == EINA_VALUE_TYPE_ERROR)
{
Eina_Error err;
eina_value_get(&value, &err);
if ((!expected_err) || (expected_err == err))
{
DBG("ignored error %d (%s)", err, eina_error_msg_get(err));
return EINA_VALUE_EMPTY;
}
}
return value;
}
EAPI Eina_Future_Desc
eina_future_cb_ignore_error(Eina_Error err)
{
return (Eina_Future_Desc){ _eina_future_cb_ignore_error, (void*)(uintptr_t)err, NULL };
}
EAPI void
eina_future_desc_flush(Eina_Future_Desc *desc)
{
if (!desc) return;
_fake_future_dispatch(*desc, ECANCELED);
memset(desc, 0, sizeof(Eina_Future_Desc));
}
EAPI void
eina_future_cb_easy_desc_flush(Eina_Future_Cb_Easy_Desc *desc)
{
if (!desc) return;
if (desc->error)
{
Eina_Value r;
r = desc->error((void *)desc->data, ECANCELED);
eina_value_flush(&r);
}
if (desc->free) desc->free((void *)desc->data, NULL);
memset(desc, 0, sizeof(Eina_Future_Cb_Easy_Desc));
}
static Eina_Value
_future_cb_log(void *data, const Eina_Value value,
const Eina_Future *dead EINA_UNUSED)
{
Eina_Future_Cb_Log_Desc *ctx = data;
const char *content = "no value";
char *str = NULL;
EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, value);
if (value.type)
{
str = eina_value_to_string(&value);
content = str;
}
eina_log_print(ctx->domain, ctx->level,
ctx->file ? ctx->file : "Unknown file",
ctx->func ? ctx->func : "Unknown function",
ctx->line, "%s%s%s",
ctx->prefix ? ctx->prefix : "",
content,
ctx->suffix ? ctx->suffix : "");
free(str);
free((void *)ctx->func);
free((void *)ctx->file);
free((void *)ctx->prefix);
free((void *)ctx->suffix);
free(ctx);
return value;
}
EAPI Eina_Future_Desc
eina_future_cb_log_from_desc(const Eina_Future_Cb_Log_Desc desc)
{
Eina_Future_Cb_Log_Desc *ctx = calloc(1, sizeof(Eina_Future_Cb_Log_Desc));
EINA_SAFETY_ON_NULL_GOTO(ctx, exit);
if (desc.prefix) ctx->prefix = strdup(desc.prefix);
if (desc.suffix) ctx->suffix = strdup(desc.suffix);
if (desc.file) ctx->file = strdup(desc.file);
if (desc.func) ctx->func = strdup(desc.func);
ctx->domain = desc.domain;
ctx->level = desc.level;
ctx->line = desc.line;
exit:
return (Eina_Future_Desc){ .cb = _future_cb_log, .data = ctx };
}