#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 #include #include #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__, __FUNCTION__) 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) }; EAPI const Eina_Value_Struct_Desc *EINA_PROMISE_RACE_STRUCT_DESC = &RACE_STRUCT_DESC; 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; } static 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) { // 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, ©, sizeof (Eina_Value))) copy = v; else if (!eina_value_copy(&v, ©)) { ERR("Value cannot be copied - unusable with Eina_Future: %p (%s)", v.type, v.type->name); eina_value_setup(©, EINA_VALUE_TYPE_ERROR); eina_value_set(©, 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); 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); 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_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 }; }