Re: [E-devel] [YAPT] Chaining promises

2016-10-30 Thread Gustavo Sverzut Barbieri
Hi all,

I just remembered about this thread after recent email by raster. What
jpeg points out is that it's strange, because it IS strange and thus
not right -- unlike Cedric said.

When you fulfill a promise (ie: feed a value to Future, delivered via
the callback specified in efl_future_then()), you send a value.

This value can be:
 - an actual value, called 'resolved'
 - another promise.

This can be implemented with Eina_Value, by defining a new
Eina_Value_Type for future. Thus the function signature is plain
simple as in JS:

 eina_promise_fulfill(p, eina_value);

That said, take the other, the reader/future side:

f = some_function_returning_a_future_for_a_promise();
f2 = efl_future_then(f, cb1);
f3 = efl_future_then(f, cb2);

Internally, these future are a linked list:

   f->next = f2;
   f2->next = f3;

This is built inside efl_future_then().

In the above example "f" is the only future with an associated
promise. Others are 'shallow':

   f->promise = 
   f2->promise = NULL
   f3->promise = NULL

That is cross-linked with the promise:

   ->future = f


Back to the WRITER (promise) side, when you fulfill:

   efl_promise_fulfill(promise, value) {
  future = promise->future;
  promise->future = NULL; // avoid access to future of this promise again

  // let people NULL their pointers
  report_weak_refs(future);
  report_weak_refs(promise);

  next_value = future->fulfill(p->future, value);
  if (!next_value) {
  // report error, similar to 'fulfill' of an actual value
procedure below:
  // find first 'future->error', call it from a job.
  }
  if (eina_value_type_get(next_value) == EINA_VALUE_TYPE_FUTURE) {
  eina_value_get(next_value, _future);

  // value_future is a recently created future and will
  // NEVER have 'fulfill' or 'error' set, if they do, it's a
bug (ERR...)
  // MAYBE they have weak refs due bindings (unref them).
  // ---
  // OTOH, future->next WILL likely have fulfill/error/weak-refs
  // since it was created by efl_future_then()
  // thus we steal the promise and set it in it

  if (future->next) {
 next_future = future->next;
 // steal
 next_future->promise = value_future->promise;
 next_future->promise->future = next_future;

 if (value_future->fulfill || value_future->error) {
  ERR("the future value must NOT have an associated
error/fulfill! future=%p", value_future);
  // maybe call error in value_future and chain
value_future->next ...?
 }
 value_future->promise = NULL;
 report_weak_refs(value_future);
 eina_value_free(next_value); // free it
 return; // need to wait for fulfill or reject/error
  } else {
 return; // if it was the last 'fulfill'/'error', do not free
   // just let it do its work and then
fullfill/reject to nothing
  }
  } else { // actual value, 'resolved'

  // find first future with an "fulfill", values are propagated.
  while ((future) && (!future->fulfill)) {
 // this future has no promise, so we just call it done:
 report_weak_refs(future);
 next_future = future->next; // do it after weak refs,
eventually 'next' is gone via 'cancel'
 free(future);
 future = next_future;
   }

  // no future to receive the data, we're done
  if (!future) {
  eina_value_free(next_value); // not used, bye bye
  return;
  }

  ctx = malloc(sizeof(*ctx));
  ctx->future = future;
  ctx->value = value;
  job_add(future_fulfill_job, future_fulfill_canceled, ctx);
// do it from next main loop iteration, "clean context"
  }
  }
   }

   future_fulfill_job(data) {
  ctx = data;
  report_weak_refs(future);

  next_value = ctx->future->fulfill(ctx->future, ctx->value);

  // ---
  // HERE GOES THE SAME (or similar) code to the one in
efl_promise_fulfill()!!!
  // ensures the chaining
  // ---

  // no 'eina_value_free()', value is owned by 'then'!
  free(ctx->future);
  free(ctx);
   }

   future_fulfill_canceled(data)  { // main loop del...
   eina_value_free(ctx->value);
   report_weak_refs(ctx->future);
  // maybe ctx->future->error() ECANCELED?
   free(ctx->future);
   free(ctx);
   }



With you example, it would actually be:

main_func(url) {
  // this specifies ALL the pipeline:
  Efl_Future *f1 = download_url(url);
  Efl_Future *f2 = efl_future_then(f1, _down_cb); // would be better
called 'do_unzip'
  Efl_Future *f3 = efl_future_then(f2, _unzip_cb, image); // would be
better called 'do_load'
  efl_future_then(f3, _load_cb, image); // do_show
}

Eina_Value *_down_cb(void *data EINA_UNUSED, Eina_Value 

Re: [E-devel] [YAPT] Chaining promises

2016-09-30 Thread Cedric BAIL
Hi,

Le 30 sept. 2016 07:51, "Jean-Philippe André"  a écrit :
> Here's a question about promise chaining. C being what it is, it's harder
> to define a series like p.then().then() than it is in JS. And I can't find
> any good example in EFL code.
>
> Let's consider an abstract chain that I'll define as "download, unzip,
> show". (eg. download a zip file, unzip it and set the file on an image
> object).
>
>
> All of these are async, we don't care about the result of show. I don't
> understand how the chain should be written:
>
> main_func(url) {
>   Efl_Future *f1 = download_url(url);
>   Efl_Future *f2 = efl_future_then(f1, _down_cb, ...,  NULL);
>   efl_future_then(f2, _unzip_cb, ..., image);
> }
>
> // So far I think we're ok
>
> void _down_cb(null, event) {
>Efl_Promise *next = event->info->next;
>Efl_Future *f3 = _async_unzip(event->infodata);
>efl_future_then(f3, _unzip_cb2, .., next);
> }
>
> // I'm already getting confused with next, f2 and f3. Something is odd.

Yes, that's why there is some function (I don't have the code at end), to
feed a future into a promise. This connection take care of propagating the
value to the next promise. It avoid having to setup your own callback to do
so as you did in this example.

> void _unzip_cb(data, event) {
>Image *image = data;
>show(image);
> }
>
> void _unzip_cb2(data, event) {
>Efl_Promise *p2 = data;
>efl_promise_value_set(p3, NULL, NULL); // --> calls unzip_cb
> }
>
>
> So my questions:
> 1. Is this above code correct?

Yes.

> 2. (How) are we ensuring that the chain eventually resolves?

You mean, that if you don't set anything on the next promise, nothing will
happen ? There is nothing there we can do I think, as everything being
asynchronous, we can't know if there is a good reason that the value hasn't
been set after we leave the callback.

Possible hack for this is to unref the next promise. If the callback hasn't
refed it nor set a failure/value, it will trigger an error and trigger a
failure on the next future. Doable, but not sure if the value.do you think
it's worth it ?

> 3. Couldn't this be made in a simpler way? unzip_cb2 is really just an
> automatic resolve of another promise/future.

Yes, as said before, chaining imply we should be able to set a future as a
value on a promise.

> In unzip_cb, if I ignore "next", f2 will never come to completion.
>
> So here's a proposal:
>
> 1. Get rid of info->next. Instead:
>
> Efl_Promise *p2 = efl_promise_next(event->object);
>
> That way, EFL knows that the developer has not ignored the promise. The
> developer must then call efl_promise_value/error_set at some point, sync
or
> async.

This can not work.every set of callback need a different next promise
otherwise only one of them can really use it. For the same reason, multiple
st if callback are useful, we need to associate multiple next promise.

> If the callback doesn't call efl_promise_next, EFL knows that the "next"
> promise will never come to completion, ever. In that case, EFL
> automatically calls efl_promise_value_set(next, NULL, NULL) to keep
walking
> the chain. (Another idea is to instead cancel it).

See above proposition to do the same and still have multiple callback.

> According to [ https://promisesaplus.com/ ], if onResolve is not a
function
> (success_cb is NULL), then p2 is fullfilled with the same value as p1. So
I
> guess we would just call _unzip_cb directly in the above example.

Yes, that's something I plan to handle, setting null function well just
propagate the value/error/process down the chain.

> Without efl_promise_next() the callback must always handle "next". Failure
> to do so will break all chains, and leak. The value of "next" can be set
> inside _efl_loop_future_success.

Yes, but it's kind of a false problem anyway. If you set expect a future
and never get it triggered and you're the one who is in charge of the
promise, you have maximum 2 places where you may have forgotten to set a
value/trigger a failure. We can improve the detection as proposed above,
but I don't think it's a real problem.

> 2. (raster's idea) Get rid of event->info
>
> Similarly, use efl_promise_value_get() instead of event->info->value. That
> way we can implement stealing with efl_promise_value_steal(,
)
> and keep the symmetry with value_get().

I am not a fan of area stealing value. It may create slight problem with
multiple callback and other user have to be aware of what you're doing
(order if registering callback also matters in that case). So I would think
that if you want the ability to steal a value, we should actually have a
different efl_future_steal that allow only one callback and prevent any
other efl_future_then to be registered (and trigger an error if any are).
Which means, that value get is pretty much unnecessary. We can have it, as
it solve the issue we have by reusing efl_event_cb (but maybe we should try
to solve that instead of going around it).

> In that case, 

[E-devel] [YAPT] Chaining promises

2016-09-29 Thread Jean-Philippe André
Hi,


Here's a question about promise chaining. C being what it is, it's harder
to define a series like p.then().then() than it is in JS. And I can't find
any good example in EFL code.

Let's consider an abstract chain that I'll define as "download, unzip,
show". (eg. download a zip file, unzip it and set the file on an image
object).


All of these are async, we don't care about the result of show. I don't
understand how the chain should be written:

main_func(url) {
  Efl_Future *f1 = download_url(url);
  Efl_Future *f2 = efl_future_then(f1, _down_cb, ...,  NULL);
  efl_future_then(f2, _unzip_cb, ..., image);
}

// So far I think we're ok

void _down_cb(null, event) {
   Efl_Promise *next = event->info->next;
   Efl_Future *f3 = _async_unzip(event->infodata);
   efl_future_then(f3, _unzip_cb2, .., next);
}

// I'm already getting confused with next, f2 and f3. Something is odd.

void _unzip_cb(data, event) {
   Image *image = data;
   show(image);
}

void _unzip_cb2(data, event) {
   Efl_Promise *p2 = data;
   efl_promise_value_set(p3, NULL, NULL); // --> calls unzip_cb
}


So my questions:
1. Is this above code correct?
2. (How) are we ensuring that the chain eventually resolves?
3. Couldn't this be made in a simpler way? unzip_cb2 is really just an
automatic resolve of another promise/future.

In unzip_cb, if I ignore "next", f2 will never come to completion.




So here's a proposal:


1. Get rid of info->next. Instead:

Efl_Promise *p2 = efl_promise_next(event->object);

That way, EFL knows that the developer has not ignored the promise. The
developer must then call efl_promise_value/error_set at some point, sync or
async.

If the callback doesn't call efl_promise_next, EFL knows that the "next"
promise will never come to completion, ever. In that case, EFL
automatically calls efl_promise_value_set(next, NULL, NULL) to keep walking
the chain. (Another idea is to instead cancel it).

According to [ https://promisesaplus.com/ ], if onResolve is not a function
(success_cb is NULL), then p2 is fullfilled with the same value as p1. So I
guess we would just call _unzip_cb directly in the above example.

Without efl_promise_next() the callback must always handle "next". Failure
to do so will break all chains, and leak. The value of "next" can be set
inside _efl_loop_future_success.



2. (raster's idea) Get rid of event->info

Similarly, use efl_promise_value_get() instead of event->info->value. That
way we can implement stealing with efl_promise_value_steal(, )
and keep the symmetry with value_get().

In that case, efl_ref(promise) can be used to keep a value alive for as
long as you want. It's just a storage object. [if not eo, then promise_ref,
whatever]
Any weak ref by efl_promise_use probably needs to be reset to NULL after
the success/fail callback.



3. multiple then, race, all order of operations
WIth steal, and any kind of cleanup callback, the order of operations is
crucial. Is it well defined? Example:

f1 = function();
efl_future_then(f1, _then1, ...);
efl_future_then(f1, _then2, ...);
efl_future_then(f1, _then3, ...);
// then1, then2, then3 MUST be called in this order (I think)

f2 = function();
efl_future_then(efl_future_all(f1, f2), _then4);
efl_future_then(f2, _then5, ...);
// will it be _then4, _then5 or _then5, _then4?
// same question for race
// I believe the implementation is fine here, but not many docs/tests/uses
:(



Thoughts?
Is the above OK or completely off track?


PS: Sorry this mail became longer than I thought at first :-/


-- 
Jean-Philippe André
--
___
enlightenment-devel mailing list
enlightenment-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/enlightenment-devel