diff --git a/src/lib/eina/eina_promise.c b/src/lib/eina/eina_promise.c index fbb878447d..c3b488d223 100644 --- a/src/lib/eina/eina_promise.c +++ b/src/lib/eina/eina_promise.c @@ -315,11 +315,15 @@ _eina_promise_cancel(Eina_Promise *p) } static void -_eina_promise_value_steal_and_link(Eina_Value value, Eina_Future *f) +_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); } @@ -377,7 +381,7 @@ _eina_value_is(const Eina_Value v1, const Eina_Value v2) } static void -_eina_future_dispatch(Eina_Future *f, Eina_Value value) +_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); @@ -386,7 +390,7 @@ _eina_future_dispatch(Eina_Future *f, Eina_Value value) 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(next_value, NULL); + _eina_promise_value_steal_and_link(scheduler, next_value, NULL); } else eina_value_flush(&next_value); return; @@ -401,19 +405,22 @@ _eina_future_dispatch(Eina_Future *f, Eina_Value value) eina_value_pget(&next_value, &p); DBG("Future %p will wait for a new promise %p", f, p); } - _eina_promise_value_steal_and_link(next_value, f); + _eina_promise_value_steal_and_link(scheduler, next_value, f); } - else _eina_future_dispatch(f, next_value); + 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(f, value); + _eina_future_dispatch(scheduler, f, value); } void @@ -471,6 +478,11 @@ _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); @@ -495,7 +507,8 @@ _eina_promise_deliver(Eina_Promise *p, { Eina_Future *f = p->future; _eina_promise_unlink(p); - if (value.type == &EINA_VALUE_TYPE_PROMISE) _eina_promise_value_steal_and_link(value, f); + 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 @@ -598,7 +611,8 @@ _eina_promise_clean_dispatch(Eina_Promise *p, Eina_Value v) { _eina_promise_value_dbg("Clean contenxt - Resolving promise", p, v); _eina_promise_unlink(p); - _eina_future_dispatch(f, v); + // This function is called on a promise created with a scheduler, not a continue one. + _eina_future_dispatch(p->scheduler, f, v); } eina_mempool_free(_promise_mp, p); } @@ -693,6 +707,25 @@ eina_promise_new(Eina_Future_Scheduler *scheduler, 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) { diff --git a/src/lib/eina/eina_promise.h b/src/lib/eina/eina_promise.h index cb5c796317..bff5036214 100644 --- a/src/lib/eina/eina_promise.h +++ b/src/lib/eina/eina_promise.h @@ -531,6 +531,7 @@ struct _Eina_Future_Desc { * @return A promise or @c NULL on error. * @see eina_future_cancel() * @see eina_future_new() + * @see eina_promise_continue_new() * @see eina_promise_resolve() * @see eina_promise_reject() * @see eina_promise_data_get() @@ -542,6 +543,72 @@ struct _Eina_Future_Desc { */ EAPI Eina_Promise *eina_promise_new(Eina_Future_Scheduler *scheduler, Eina_Promise_Cancel_Cb cancel_cb, const void *data) EINA_ARG_NONNULL(1, 2) EINA_WARN_UNUSED_RESULT; +/** + * Creates a new promise from a dead_future. + * + * This function creates a new promise from a future currently being resolved which can be + * used to create a #Eina_Value with eina_promise_as_value(). Every time a promise is + * created a #Eina_Promise_Cancel_Cb must be provided which is used to free resources + * that were created. + * + * A promise may be canceled directly by calling + * @c eina_future_cancel(eina_future_new(eina_promise_new(...))) + * that is, cancelling any future that is chained to receive the results. + * + * However promises can be canceled indirectly by other entities. + * These other entities will call `eina_future_cancel()` themselves, + * however you may not be aware of that. Some common sources + * of indirect cancellations: + * + * @li A subsystem was shutdown, cancelling all pending futures (ie: ecore_shutdown()) + * + * @li An EO object was linked to the promise or future, then if the object dies (last reference + * is gone), then the pending promises and futures will be canceled. + * + * @li Some other entity (library provider or library user) chained and canceled his future, + * which will result in your future being canceled. + * + * Since a promise may be canceled indirectaly (by code sections that goes beyond your scope) + * you should always provide a cancel callback, even if you think you'll not need it. + * + * Here's a typical example: + * + * @code + * + * Eina_Value + * _future_resolve(void *data, const Eina_Value v, const Eina_Future *dead_future) + * { + * Eina_Promise *p; + * p = eina_promise_continue_new(dead_future, _promise_cancel, NULL); + * return eina_promise_as_value(p); + * } + * @endcode + * + * If you already have a value and want to create a future that will + * resolve to it directly use the eina_future_resolved(), it has the + * same effect as creating a promise and immediately resolving it. + * + * @note This function is to be used solely inside of a future resolve callback with + * the Eina_Value being returned from it. + * + * @param dead_future The future being resolved to get a scheduler from. + * @param cancel_cb A callback used to inform that the promise was canceled. Use + * this callback to @c free @p data. @p cancel_cb must not be @c NULL ! + * @param data Data to @p cancel_cb. + * @return A promise or @c NULL on error. + * @see eina_future_cancel() + * @see eina_future_new() + * @see eina_promise_new() + * @see eina_promise_resolve() + * @see eina_promise_reject() + * @see eina_promise_data_get() + * @see eina_promise_as_value() + * @see #Eina_Future_Scheduler + * @see #Eina_Future_Scheduler_Entry + * @see #Eina_Future_Scheduler_Cb + */ +EAPI Eina_Promise *eina_promise_continue_new(const Eina_Future *dead_future, Eina_Promise_Cancel_Cb cancel_cb, const void *data) EINA_ARG_NONNULL(1, 2) EINA_WARN_UNUSED_RESULT; + /** * Gets the data attached to the promise. *