On Mon, Mar 12, 2018 at 1:01 AM, Carsten Haitzler <ras...@rasterman.com> wrote:
>
> On Sun, 11 Mar 2018 16:49:37 -0300 Gustavo Sverzut Barbieri
> <barbi...@gmail.com> said:
>
> > On Sat, Mar 10, 2018 at 6:42 AM, Carsten Haitzler <ras...@rasterman.com>
> > wrote:
> > > On Fri, 9 Mar 2018 10:52:59 -0300 Gustavo Sverzut Barbieri
> > > <barbi...@gmail.com> said:
> > >
> > >> On Thu, Mar 8, 2018 at 4:58 AM, Carsten Haitzler <ras...@rasterman.com>
> > >> wrote:
> > >> > On Thu, 1 Mar 2018 14:11:26 -0300 Gustavo Sverzut Barbieri
> > >> > <barbi...@gmail.com> said:
> > >> >
> > >> >> On Wed, Feb 28, 2018 at 2:33 AM, Carsten Haitzler 
> > >> >> <ras...@rasterman.com>
> > >> >> wrote:
> > >> >> > On Tue, 27 Feb 2018 18:34:59 -0500 Cedric Bail <ced...@ddlm.me> 
> > >> >> > said:
> > >> >> >
> > >> >> >> -------- Original Message --------
> > >> >> >>  On February 27, 2018 2:52 PM, Carsten Haitzler
> > >> >> >> <ras...@rasterman.com> wrote:
> > >> >> >>
> > >> >> >> >so I'm implementing a new efl.exe class (and efl.task and some
> > >> >> >> >others) and i
> > >> >> >> > WAS going to use a future as the return for run() ... but after
> > >> >> >> > just writing about 20 lines of code (get a scheduler, create a
> > >> >> >> > promise alloc promise data and all the checking in between) and i
> > >> >> >> > then realized... it's ballooning to an insane amount of code vs
> > >> >> >> > event_callback_call which is a 1 line func call when the event
> > >> >> >> > happens.
> > >> >> >> >
> > >> >> >> > let me just copy and paste the relevant lines i had sketched out
> > >> >> >> > (was not final or even compiling yet):
> > >> >> >> >
> > >> >> >> > typedef struct _Efl_Exe_Run_Data
> > >> >> >> > {
> > >> >> >> > Eo *obj;
> > >> >> >> > } Efl_Exe_Run_Data;
> > >> >>
> > >> >> not sure you need this
> > >> >
> > >> > i need to report the object the future is for with the future... and i
> > >> > don't want to rely on people having to use the data ptr for that. this
> > >> > is probably one of the most common needs/uses of a future - that it's a
> > >> > one off action FOR an object.
> > >>
> > >> well, the thing is: promise is about a value, not anything else... if
> > >> we start to misuse, it start to become less useful.
> > >
> > > actually it's about calling success or failure at some point in the future
> > > and standardizing this so you can have those all/race things and chain a
> > > then b then c etc.
> > >
> > > value is actually secondary to this.
> >
> > not really, value is core part of it :-)
>
> i guess we disagree, but the call without the call to the success or 
> failure...
> value is pointless as you don't know what it is, so the calls come first in
> importance, value second.

ok, we agree to disagree... but even as "value second" you recognize
it's important, so ok :-)


> > >> for instance, with events rarely we carry payload info, usually one
> > >> queries from the object... like text changed on widgets.
> > >>
> > >> however for promises, with the chaining idea, rarely you should be
> > >> getting the owner object... in the rare cases you need it, use "the
> > >> closure" (void *data), which BTW is specific for each future in the
> > >> chain, then you can chain multiple and "pass thru" the value if you
> > >> need multiple objects (ie: say you need to get a value and show it to
> > >> 2 label objects, you can connect 2 cb in a chain, each with a label
> > >> object.
> > >
> > > actually it's going to be incredibly common. example:
> > >
> > > file_set on an image object... what do you think people want to do? they
> > > want to then SHOW the object or emit a signal to it or something. they 
> > > want
> > > to initiate some response.
> > >
> > > for exe's and threads the object will not be magically deleted if the exe
> > > exits or thread exits. the object has to exist firstly to collect the
> > > results (i/o, exit code results etc.) - it has fd's it has to listen on
> > > etc. etc. ...
> >
> > not sure if you checked the efl_future (EFL, not EINA) version I told
> > you... the object goes there for you... no need for you, the promise
> > creator, to do that...
> >
> > the promise create should only care about resolving a value or rejecting it.
>
> in this case it'd be resolving the result of an action of an object - same as
> file_set. the object is the key thing here, not the value.

here, since the loaded data is tied to the object... sure... but the
promise should return none, like "job" or "timeout"... you get a
notification the action completed, not the object that originated it
(ie: job is the mainloop).

take my word, the object is better passed elsewhere. You may not get
it now, but using it a little bit you'll.

what I'd agree to change is Efl_Future_Cb_Desc. Currently it carries
the obj, but no extra data (as you could hook the data in obj, using
keys...). But I'd not oppose to offer an extra "void *data" there.

https://git.enlightenment.org/core/efl.git/tree/src/lib/eo/Eo.h#n367


> > > if the object is deleted the promise cannot "continue" and report success 
> > > or
> > > failure. it won't know. all tracking of the child is deleted. i have 
> > > chosen
> > > for now at least that deletion doesn't block and wait for a child to exit
> > > nor initiate any exiting (of thread or exe).
> >
> > this breaks promises completely. You need to pick a concept and stick
> > to it. For example, if we're allowing this. "detach" thing, then add a
> > new ERROR_DETACHED or the likes and use that in every promise that may
> > be disconnected.
>
> it's not that practical to keep the promise going after the object is gone.
> same for file_set on an object. why does it or should it continue after the
> object the action is fore has gone away? it should just fail at this point as
> there is no point delivering the result of a file_set on an image object if 
> the
> object has gone - same as the exit result of an exe obj if the exe obj doing
> the tracking of the child has gone.

man, we're talking the same thing here... I guess when I tried to
explain I went to far and you missed what I wrote.

but sure, we agree... as it's now, if you bind the promise to an
object (ie: efl_future_Eina_Future_XXX_then, which will be automatic
from eolian), then once the object dies, the futures are cancelled!
all promises are rejected with ECANCELED.


> > However, as I wrote before, more than just doing that, allowing the
> > user to control the behavior would be nice, given the very rare cases
> > detach is expected could do 1 prop-set more to get desired results.
>
> right now "you just aren't allowed to do this unless you no longer care about
> the exe". deletion == you don't care anymore.

here I'm not sure we're talking the same.

if I delete an exe *without* calling "end()": does it auto-end to you?

It's similar to my Efl.Io.Closer.close_on_destructor. That's the kind
of flag I'm talking about... 99% of the cases, "close_on_destructor"
is what you want, whenever you  efl_del()/unref-the-last-ref, it
behaves just like you called efl_io_closer_close(obj).

For tasks it's the same. 99% (and .9999....) of the cases you want the
task to be ended when you delete the object... ie: once the mainloop
ends, deletes all chidren tasks, which kills all processes... It's
analogous to when you fork() but don't detach. To "fork-a-daemon" you
need to explicitly detach to make it happen.


> > > but... once the thread or exe has exited... the object SHOULD then be
> > > deleted. otherwise it hangs around forever consuming memory for no reason
> > > other than to store an exit code and any other data the user may have
> > > attached to it.
> >
> > here you're mixing things. It traces back to the other discussion
> > about invalidate x reference... the object should be invalidated,
> > sure... not deleted as refcounts shouldn't be messed up.
>
> i can't control if someone dels or unrefs the object to 0. i can only control
> what is done about it. i'm talking about that case where the object will be
> destroyed etc.

as per above. I'd add a end_on_destructor = true (default).


> > then check that discussion on "release ref on invalidate"
> >
> > it's not particular of promise/future.
> >
> >
> > > thus the object should always be provided to the future (success or 
> > > failure)
> > > because it is basically expected that you "do something about it" and 
> > > delete
> > > the object. if you do not then it'll leak. if you manage to somehow chain
> > > things so you delete something else (a parent object etc.) and then this
> > > gets magically deleted along with that - great. but most of the time you
> > > have to handle the deletion manually because of autodel being bad for c++
> > > etc.
> >
> > that's because you're solving the wrong problem at the wrong layer.
> > See above, mixing invalidate x ref...
> >
> > last but not least, if you read my examples above, a single future can
> > solve this in every case:
>
> what examples above? i see this below...

sorry :-D

[...]

> > >> >> >> > static void
> > >> >> >> > _efl_exe_run_cancel(void *data, const Eina_Promise *dead_ptr
> > >> >> >> > EINA_UNUSED) {
> > >> >> >> > Efl_Exe_Run_Data *d = data;
> > >> >> >> >
> > >> >> >> > efl_task_end(d->obj);
> > >> >> >> >}
> > >> >> >> >
> > >> >> >> > Efl_Exe_Run_Data *d;
> > >> >> >> > Eina_Promise *p;
> > >> >> >> >
> > >> >> >> > d = calloc(1, sizeof(Efl_Exe_Run_Data));
> > >> >> >> > EINA_SAFETY_ON_NULL_RETURN_VAL(d, NULL);
> > >> >> >> > d->obj = obj;
> > >> >> >> >p = eina_promise_new(sched, _efl_exe_run_cancel, d);
> > >> >> >> > EINA_SAFETY_ON_NULL_RETURN_VAL(p, NULL);
> > >> >> >> > d->promise = p;
> > >> >> >> >d->run_future = efl_future_Eina_FutureXXX_then(obj,
> > >> >> >> >eina_future_new(p)); return d->run_future;
> > >> >> >>
> > >> >> >> You do not need to keep the future at all in your structure. You 
> > >> >> >> are
> > >> >> >> good to go with just :
> > >> >> >>
> > >> >> >> return efl_future_Eina_FutureXXX_then(obj, eina_future_new(p));
> > >> >> >
> > >> >> > then how do i trigger success or failure of the future if i don't
> > >> >> > have a handle on it - well promise/future... whatever. same thing to
> > >> >> > me. i dislike the whole division of promise vs future. to me it's an
> > >> >> > async task that at some point in the future triggers a success or
> > >> >> > failure then disappears after that.
> > >> >>
> > >> >> you just keep the write/send side: Eina_Promise: p... that's where you
> > >> >> send your results.
> > >> >
> > >> > well i need to keep a handle to something. that's my point.
> > >>
> > >> for this particular case you shouldn't need "d". call
> > >> eina_promise_new() without any data OR with the handle used to
> > >> *cancel* the promise, that's it and only it :-)
> > >
> > > i need a handle so i can fulfil the promise later (or cancel on object
> > > deletion and thus fail). without that i can't pass on the result.
> >
> > in your task case, since you can't run more than once, this can go
> > into the pd->promise...
>
> correct. so my point still stands - i do need to track this extra handle.
> that's 1 more struct member, 1 line to NULL it after it's resolved just to 
> clean
> up (though not needed strictly as i've forbidden multiple runs - a 2nd run
> will fail now).
>
> but ok - this extra code is kind of expected as i have to rack a specific
> "thing".

one thing we can do to help is like done for Future, specify a
"store_ptr" that is auto-nulled... saves the "null part", still need
to add it to the structure, sure :-D

however not sure it's that valuable...


> > >> also, don't keep the reference to d->run_future... it's wrong, you're
> > >> not supposed to ever touch it anymore, the ownership is passed to the
> > >> caller...
> > >>
> > >> it's being misused... the one that creates the promise, only keep the
> > >> promise... you don't even need to store it in pd or obj in any way,
> > >> since efl_future (not EINA) will bind it to the object, whenever the
> > >> object dies the promise will be automatically cancelled.
> > >
> > > ok. that certainly wasn't clear. but i still need something so when the 
> > > exit
> > > happens i trigger the promise success or failure (exit code 0 == success,
> > > anything else, failure)
> >
> > note that this needs to be a convention. For instance, most HTTP
> > frameworks do NOT reject the promise on HTTP status not in
> > 200-range... their convention is that it will return the promise with
> > the status. Rejection just occurs on network failures (ie: the HTTP
> > request didn't finish).
>
> at least for the case where the obj is deleted (goes to 0 references.
> destructor called) i don't think keeping the promise running is sensible (for
> now - it can be de-limited later if enough code is written to split the obj
> from the tracking).

sure, you'd not!  promise should be rejected with ECANCELED...
actually if the obj is bound (like efl_future...), it's automatically
done.


> > If you ask me, for processes, rejecting on status != 0 is bad since it
> > will be harder to pass the value (there is no standard for you to
> > convert that to an Eina_Error, or "Exception" or "Error" in other
> > languages). And some people do use return status as "0" (all right)
> > but others to indicate the kind of failure (ie: 1 = invalid params, 2
> > = failed resources, ...). These won't be able to use your promise.
>
> but != 0 is that a process failed. "file not found" or "io error during cp"
> etc. etc. - the shell considers it a failure when you do
>
> cmd1 && cmd2 && cmd3
>
> which is analogous to
>
> cmd1.run().then(cmd2.run().then(cmd3.run()))
>
> cmd2 will never run if cmd1 returns non-0...
>
> indeed the return value if not 0 indicates the kind of error. there isn't a
> standard to convert that indeed other than "that's an error" which is about 
> all
> I can do. say it's an error and offer the code.

okay, you're right but shell also offers:

cmd
if [ $? -eq 1 ]; then...

which some people use.


> > that's the reason why the http/promise people use that, so 50x is
> > handled on way, 40x is another, etc. very similar proble.
>
> the problem with exe's is non-zero could be "command not found" too. it's an
> error tat tells you the desired result of the execution failed somehow... it's
> pretty coarse. :(

here it's something interesting. "command not found" is not triggered
*after* the command runs, OTOH: exec() fails... this is one of the
cases where you're reject the promise. The other is when fork() fails.

So basically, if we follow the promise/http convention:

  - fork or exec failed: reject
  - process exit (0 or not): resolve

Since we lack easy to use lambda, what we can use is a future comparison:

    eina_future_cb_compare_to(other_value, enum eina_compare_result);
    enum eina_compare_result { less, less_equal, equal, greater_equal,
greater, different };

Usage:

   eina_future_chain(efl_task_run(t),
      eina_future_cb_compare_to(eina_value_init_init(0), eina_compare_equal),
      my_actual_future);

with that, if task ends != 0, it's failed.


   eina_future_chain(efl_task_run(t),
      eina_future_cb_compare_to(eina_value_init_init(0),
eina_compare_different),
      my_actual_future);

with that, if task ends == 0, it's failed.



> > >> all you need is to pass "p" (the promise!) to the task resolution...
> > >> once it's resolved/done, call eina_promise_resolve().
> > >
> > > yes - so i need to keep that around. either way i need to keep something.
> > > that is my point.
> >
> > you'd need that anyway since you can't allow more than one run to exist...
>
> yes. that was my point - i have to keep the handle around so i can fulfil it
> later. :) you were telling me i don't need to do this. :)

well, in your code you keep the "run" flag around... it's the same.

IF you had another event handler, say "on_child" event... then you
could pass the promise as callback data there and no need to store the
promise.


> > >> if you have the full code for what you're doing, including when "it
> > >> should resolve", I can pinpoint how to do it. Maybe send a Phab ticket
> > >> and mark me.
> > >
> > > https://git.enlightenment.org/core/efl.git/tree/src/lib/ecore/efl_exe.c#n188
> >
> > 1) if you're using promise, why are you emitting an event? Remove that
>
> i'm NOT using a promise. that was the original email. i started writing the
> code tyo do the promise got to about 20 lines and went "WTH? this is insane"
> and just made it an event. i quote the original:
>
> "i got to 22 lines and i wasn't even done yet (need to do some more
> housekeeping)... vs 1 line for event_callback_call. i'm going with events 
> until
> futures/promises are not a crazy amount of code compared to events. this is it
> with events:
>
>    efl_event_callback_call(obj, EFL_TASK_EVENT_EXIT, NULL);"
>
> :)

ok, what about if you move to that now that you got more details?


> > 2) there is no need to keep exit_called
> > 3) there is no need to efl_ref
>
> since it's a job - there is. the exe obj may be deleted and thus the job 
> called
> with no exe obj around. well.. there is nothing that tells me if the future
> will or will not be magically cancelled if obj (the data ptr) is deleted. as
> its a generic void * i'm not making that assumption.

ahhhhh... but You're saying "single line for events"... however this
is not needed for promises! ;-)

(and if you listen to me, not even the "ref" as it's managed outside).



> > 4) there is no need to efl_job, promises are already resolved in the
> > next mainloop iteration (clean context)
>
> they don't resolve in-line there and then like callback_call() ?

exactly: promise delivery requires a clean context. Always... that's
why we need the scheduler... so we know how to do it.

See https://promisesaplus.com/#point-34

   onFulfilled or onRejected must not be called until the execution
context stack contains only platform code

in EFL implementation the only difference is error handling *while
creating the future or promise itself*. That is:

   eina_future_then(bla_future_create(x),  my_future_handler);

if bla_future_create() FAILS to create a promise or future, or if
eina_future_then() fails to create a future (ie: memory allocation),
then my_future_handler is called immediately with ENOMEM.

If eina_future_then() or eina_future_chain() fails at any creation
(ENOMEM), then the whole chain is cancelled -- as expected. This means
the promise "cancel_cb" is called.

https://git.enlightenment.org/core/efl.git/tree/src/lib/eina/eina_promise.c#n831

we just don't do it from clean context since ENOMEM happened and we
can't do much about it.


> > your _exit_eval should be:
> >
> > if (pd->promise) {
> >    eina_promise_resolve(pd->promise, eina_value_int_init(status));
> >    pd->promise = NULL;
> > }
> >
> > that's all. Also it seems you're trying to call exit_eval too early,
> > ie from broken pipe... shouldn't you let the process exit, be
> > collected by waitpid() and JUST THEN you call the promise resolve from
> > a single place?
>
> aaaah it's a race condition. the eval checks that all stdout has finished 
> being
> read from the process and that now errors on reading AND has to check the
> exit code has come in. when BOTH of these have happened, then you get the 
> exit.
>
> the user of the api doesn't need to worry about this then as the race is 
> hidden
> and handled and everything serialized.

ok

[...]


> > it would help immensely if you give it a try for real... before you
> > implement the promise side (provider), give it a try as an user... get
> > some js stuff, they are full of promise, then you use to implement
> > something, then you come back with your experience...
> >
> > ie: https://github.com/axios/axios (http) + https://redux-saga.js.org
> > (state management) ?
>
> i've seen the code before. well code full of then's etc. - i know the syntax
> sugar. i'm not talking about the "consumer" side. i'm talking about the
> "producer" side inside efl.

producer side is a PITA in JS as well, not as much since they don't
offer cancel AND they use "closure" to give you the context, so you
don't need "Eina_Promise", instead they give you 2 functions
(reject/resolve) bound to the promise context... they call it
"Promise", but it's actually our "Eina_Future" since their
Eina_Promise equivalent is hidden.

but point is: do it where the most expert developers are (ie: efl core
developers), to aid non-expert developers to use it correctly.


> in C the code difference for the vast majority of cases is the same - you have
> to provide a callback. in the past i have rarely chained events in C. in edje 
> -
> sometimes. maybe 10-15% of events chain multiple anims. in C ... hardly ever.
> that's my point. it has some syntax value for SOME use cases. they are not the
> majority in my experience. if i had to chain things, i did. i didn't avoid it
> "because it was hard". it wasn't. in the callback just have 1 line to do the
> next step

You're overlooking 'chaining' here... in EFL we do chain *a lot*, but
it's not explicitly done as multiple simple functions. Rather we
create big callbacks that does it all:


bla_cb(void *data, ...) {
    My_Ctx *ctx = data;
    const xpto = a_thing(ctx->a);
    b_thing(ctx->b, xpto);
    ...
}

Usually written as promises:

eina_future_chain(bla_future_get(bla),
    a_thing_future(ctx->a), // on resolve/reject: calls
"a_thing(ctx->a)", returns the value as the future value
    b_thing_future(ctx->b)); // etc...

since we do have few cases of futures in use, the "a_thing_future"
doesn't exist, but once they start to show, things become very easy to
use, you can skip writing these yourself (you'd not need "bla_cb").


> > >> When someone say "promise you'll run this exe and return me its
> > >> result". The promise is exactly that: run until it finishes, get me
> > >> its exit code.
> > >>
> > >> In most systems, Promises are not cancellable. However in EFL we opted
> > >> to introduce the cancel behavior in order to be friendly with memory
> > >> handling (no garbage collectors and the likes) and resources (ie: why
> > >> keep working if that's to be ignored?).
> > >>
> > >> Then eina_future_cancel(future) will cancel the whole chain. One can
> > >> use this, for instance, in a Race, where the winner continues and the
> > >> other promises are cancelled. Example: race a process execution
> > >> against a timeout, to automatically stop the process if the timeout
> > >> happens.
> > >>
> > >> And note I mention the "chain", so you could have this written as:
> > >>
> > >>   - race(
> > >>       - exe -> to_string -> text_set -> enable_button
> > >>       - timeout
> > >>     )
> > >>
> > >> The exe would be executed, the exit code would be converted to a
> > >> string, displayed to a label and then a button would be enabled (ie:
> > >> "next").
> > >>
> > >> If the timeout happens, none of those would happen.
> > >>
> > >> That's the thing with chaining... while "to_string" already exists,
> > >> the others can be easily made into elm and code is simpler to write...
> > >> like lego blocks.
> > >>
> > >>
> > >> >> if the user needs the object, he can pass that as his future cb data.
> > >> >
> > >> > i disagree with this. the data should be THEIR data, not the object.
> > >> > since the object has to be deleted on exit or some point after that, 
> > >> > the
> > >> > object is needed pretty much, so why use up the only data ptr just for
> > >> > that when its a necessity?
> > >>
> > >> depending on how you do it, you don't need the object... see examples
> > >> above, the idea with chaining is that you can add more stuff easily.
> > >> So take my pipeline and add one more:
> > >>
> > >>   - exe -> to_string -> text_set -> enable_button -> unref(obj)
> > >>
> > >> the only place you need "obj" is at the end, *none* of the other
> > >> handlers would get it... and actually if it was passed, it would be
> > >> lost in the first conversion "to_string".
> > >
> > > the problem is they this is only if they manage to track that exe obj
> > > explicitly and track it down at the end of a chain of stuff... invariably
> > > 99% of the time i'd say they will want to clean it up right away when they
> > > get their exit code/result and not have to figure out how to pass it down
> > > themselves.
> >
> > in such case, just invert the order, that's the beauty of it:
> >
> >  exe -> unref(obj) -> to_string -> text_set -> enable_button
>
> but they need the obj handle. i dislike making them pass the obj handle as a
> data ptr when it's almost always going to have to be used. imagine the pain of
> evas events or eo events if you didn't have the obj already passed in and you
> had to use the data ptr for that? i can imagine the pain... it would be rather
> unpleasant. in this case the thing we are primarily talking about is the exe
> object, not the exit value. the object imho is the value. it's a container
> value.

there is where I think *VALUE* is important!

when I came to EFL I was amused why I had to get the value of the
event from the object, rather from the event itself...

EVAS_CALLBACK_MOVE: payload NONE, where to get the position? "obj"

EVAS_CALLBACK_MOVE: payload {x,y}... no use for "obj".

also, unless using very high level components that dispatch to
multiple children widgets, it's not always the case you're using the
same object down in the pipeline.

click button -> navigate to other screen: different objects.
click button -> close window: different objects.
type text -> enable "proceed" button: different objects.

etc...


> > this still works, since to_string operates on the result (int), there
> > is no need to query the value from obj (exe). But say you needed, you
> > can still reorder:
> >
> >  exe -> to_string -> unref(obj) -> text_set -> enable_button
> >
> > etc
> >
> >
> > >> > no. we can't auto-del the object no matter how much i'd love to. felipe
> > >> > would kill me. :) ...
> > >>
> > >> i guess we're talking about different things here.
> > >>
> > >>
> > >> >> the idea is that these things can be chained, including
> > >> >> transformations... like:
> > >> >>
> > >> >>   bla.run().then(uppercase).then(console.log)
> > >> >>
> > >> >> we even offer some converters and a console future for such things.
> > >> >> Actually we should even do more,
> > >> >> like those exposed by http://reactivex.io, handling multiple wait,
> > >> >> debounce, etc.
> > >> >
> > >> > i know what they are for. :)
> > >>
> > >> yes, so take that as inspiration when adding promises: what's the data
> > >> that should be passed. Don't see promises simply as "single call
> > >> events", that's wrong.
> > >
> > > they are actually both. they are single call events that follow a standard
> > > api etc. as above at the top.
> >
> > then you miss a big part of its usefulness :-(
>
> i don't see how i do...  a standard api leads to standard syntax like if (x)
> {a} else {b} but for events over time rather than  procedural logic flow. that
> makes it easier in languages like js where the syntax does come out like this,
> IF you need this.
>
> in C it doesn't come out that nice in syntax vs events realistically. they are
> much the same.
>
> they are still single call events with success or failure. when CHAINED you 
> can
> do the over-time async equivalent of
>
> if (a) {
>   if (b) {
>     if (c) {
>       party();
>     } else {
>       cry();
>     }
>     passout();
>   }
>   leave();
> }
>
> that's a logical conclusion, but it doesn't change what promises/futures are.
> they are one async if (a) {} else {}.

yes. But to compose them (multiple of those async if-else), you need
smaller blocks to pass relevant info, so you can provide pre-existing
callbacks.

in EFL/C we could go the Apple/ObjC route and enable Blocks, then at
least we can get the closures... In C++ you already have lambdas and
in high level languages they all give you some closure.


However, with closures or not, getting the important bits as result is
good to allow the pipeline to be more easily written.


> > >> the idea with future is the chain-ability... if you ignore that, it's
> > >> almost useless as it's a sub-case of "events".
> > >
> > > and that's a nice thing about them - they standardize chaining etc. thus
> > > making for nice syntax sugar etc. i know this.
> > >
> > > they are painful to DRIVE from code in C in objects which are running the
> > > show in terms of doing the i/o and finally feeding results to the 
> > > promises.
> >
> > Well, in the code I read they were painful because you overdid things.
> > The flag isn't needed, the job isn't needed, the ref isn't needed...
> > then of course, it's start to be cumbersome! :-D
>
> what happens if the exe obj is deleted then while the job future is still
> going? (for the ref).

future is rejected as cancelled.


> the exited flag i guess is superfluous as
> pd->fd.exited_read being -1 says the same thing, though this has nothing to do
> with promises or not. it's part of the race fixup above between exit signal 
> and
> the stdout from the child being all read and flushed.
>
> > if you ask me, the real PITA with promises in C is lack of lambda.
> > This is the real pain, having to declare the callbacks OUTSIDE of your
> > context, not being able to capture...
>
> that's actually why i think syntax-wise they don't come out ahead in C.
>
> > I had proposed to implement c++ lambdas in C as a pre-processor... but
> > nobody wanted to pay.
>
> there already is a pre-processor that does this. it's just a matter of using
> it. lambdapp instead of gcc or clang or cc ... :) the problem is more that 
> some
> people have major objections to having a preprocessor. "it's not C anymore if
> you need a different compile command than your normal c compiler" is the
> summary.

it's not, there is no capture... which is a major usability drawback.

Like with c++, my proposal would capture values by value or reference
(pointers) depending on your declared mode, they would be set in the
callback and once the callback finishes, if "by reference", they would
be set to their pointers so they're reflected outside.

I'd also add explicit Eo* support, so these are auto-ref/unref.


> i kind of don't agree - i think it's just augmenting the normal C 
> pre-processor
> with a more intelligent one in front of it. there's a lot of "boilerplate
> footwork" that a smarter preprocessor could cut out. like lambdas. :) i agree
> with you on this. others don't though.
>
> > nonetheless, if we start to use promises for real and correctly, we
> > can start to add some "future cb" library to make that less painful.
> > We did some examples, like type-convert and "print to console"... but
> > linking to UI would be super-nice (ie: enable/disable, text-set,
> > focus, ...). Also some flow control (ie: call this parallel chain if
> > this condition is true, ie "value == 0") and merging multiple chains
> > (ie: wait button click AND data AND timeout... then emit a tuple).
>
> that is where standardised if/else nature of promises does come in handy - you
> can build stuff on top rather generically.
>
> but my issue is not the outside use of them. i'm neither here nor there on the
> consumer side. its the producer side that is more painful.

rewrite the task_run as a promise and you'll see it's not :-)


> > and that's why I'm insisting so much on the value thing... if you do
> > have a proper value for exe, then you can get this. With that you can
> > even do some UI editor or save this as as part fo eet (ie: Gstreamer
> > offers "gst-launch" to test stuff, can load pipelines from xml...)
>
> since the actual value of return is don't generically interpretable except 0 
> ==
> success, anything else == failure... i don't see a lot of value in the "exit
> code" value. the object is of far more use than the exit code.

okay, this is more like a convention... HOWEVER I'm pointing similar
cases that after some thought moved to what I said.

if you look at the
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch

The fetch specification differs from jQuery.ajax() in two main ways:

  * The Promise returned from fetch() won’t reject on HTTP error
status even if the response is an HTTP 404 or 500. Instead, it will
resolve normally (with ok status set to false), and it will only
reject on network failure or if anything prevented the request from
completing.
...

jquery is the "first experience", it improved usability over
traditional XMLHttpRequest a lot (which uses events/callbacks)...
first version was exactly what you propose, but after some usage they
noticed it was being a pain and the "standard" became to pass the
status code as resolved (not rejected).



> > >> >> > oooh... no no no. first - i don;t need know now if the handler has
> > >> >> > been added or removed because callback_call handles that for me.
> > >> >> > and calling end() sent a
> > >> >> > signal to the child process to request it to end... it does not mean
> > >> >> > it has ended yet. so you would never sensibly call end from the exit
> > >> >> > event cb because that's sending end to a process that no longer
> > >> >> > exists... just the object representing it does still to store the
> > >> >> > results.
> > >> >> >
> > >> >> > so maybe you mean "del()" the object if the caller isn't listening 
> > >> >> > for
> > >> >> > exit events... It'd LOVE to do that, but c++ people will hate
> > >> >> > autodel... which is exactly what that would be.
> > >> >>
> > >> >> well, that's exactly what the promise does, and to consider "match
> > >> >> behavior", that's what you should do.
> > >> >
> > >> > the promise is deleted.. the exe etc. object cant be. and that object
> > >> > has to stay around because you need long term control like sending
> > >> > term/kill signals or handling i/o to and from it.
> > >>
> > >> you're not deleting the object. you're cancelling the task,  no
> > >> refcnt(exe) is changed.
> > >
> > > the caller has to delete (or unref etc.) the object. see above. if they
> > > don't it'll stay around forever.
> >
> > this is the invalidate x unref discussion from the other thread.
>
> actually no. it's just "has to go to 0 refcount so destructors are called". if
> thats via a del or unref is not relevant here. :) it has to get there one way
> or another or things leak.

what I mean is that such discussion was dealing with this part... like
instead of "auto-unref" it would just "invalidate" and let the parent
unref.



> > anyway, you can explicitly add the future to do that OUTSIDE, which
> > would enable things like this:
> >
> >   eina_future_chain(some_future_creator(x),
> >       efl_future_cb_unref(obj1),
> >       efl_future_cb_unref(obj2),
> >       efl_future_cb_unref(obj3)
> > );
> >
> > this would unref all 3 objects once the future dispatches... note
> > efl_future_cb_unref() doesn't exist, it need to be created like the
> > other helpers that returns Eina_Future_Desc...
>
> then you can only pass in obj1/2/3 via data ptr and then you cant use the data
> ptr for "your own data". :(

yep, in such case. But for most real world usages there is a "ctx" (or
globals)... So either you create a ctx, use what likely exists, etc.


> > >> cancelling the task = sending term/kill signals or i/o commands.
> > >
> > > which may eventually result in an exit from the process which then will
> > > result in the current exit event (or the promise success/cancel callbacks
> > > if those were used).
> >
> > and isn't it the desired? Just note that since the promise is already
> > rejected with ECANCELLED, it won't be rejected anymore...
>
> i'm not sure sending an int signal == cancel. it does say stop to the process.
> interrupt. but ... it may not mean the process failed. it may have done its
> job and was waiting around for more messages...
>
> i think the success or failure of the promise in this case would be based on
> the exit code alone.

not really. If you "cancel the future"... you cancelled the future,
that's what the function is called :-D

and this not related to processes or what is resolving...  it's a
convention and most people can just throw away everything once
ECANCELED.

What you mean is different: if you explicitly efl_task_end(obj), then
SURE, signal is sent to the process, which finishes and that is
resolved/rejected as the future.

different semantics even if the eina_future_cancel() happens to use
task_end in your case. That's a impl detail that shouldn't leak.



> > >> >> usually with regular events we'd just "forget" about it and leave the
> > >> >> task running, doing its process without cancelling... which is bad
> > >> >> most of the time.
> > >> >
> > >> > yes. that's why the object stays - it's not just a simple promise that
> > >> > fails or succeeds. there is an exit code, there is i/o etc. ...
> > >> >
> > >> > in theory if you don't care about the results you could delete the exe 
> > >> > or
> > >> > even thread obj and thus lose your connection to it and control 
> > >> > lines...
> > >> > but then never get a result. :)
> > >>
> > >> ok, to allow "dangling" promise you'd need to make it unbounded to the
> > >> object...  so you delete the object and the promise would still remain
> > >> alive, not "auto kill" the task.
> > >
> > > that's just not sane to do (as above - object needed to drive the i/o and
> > > listening of the exit of a thread or exe). so deletion probably has to
> > > result in the promise failing.
> >
> > here I'm talking about forcing dangling threads or processes... there
> > are cases for those, for them making a property... however they are
> > not the common cases, not even sure we should care to support these
> > anyway
>
> i think we agree on this. for now i don't want to support this as it's a fair
> bit fo extra work. but if someone does delete the object before the task has
> finished ... it needs to be dealt with sensibly. to me than means at that 
> point
> cancelling the promise that's pending.
>
> > > yes in theory i could separate the tracking out of the object and have the
> > > object just be a "view" into an internal tracker. that'd add a lot of 
> > > extra
> > > code and complexity i'm not sure is a good thing.
> > >
> > >> While I see some usage for this behavior, most of the time this is
> > >> going to lead to incorrect behavior.
> > >>
> > >> For the rare cases where this could be used, task could use the same
> > >> approach as I did for io.closer and define "auto stop on delete"
> > >> property. If true (default), once the object is deleted or the promise
> > >> is canceled, the task is stopped. If false, it's left running
> > >> dangling... with associated handlers (task/promise) freed.
> > >
> > > the problem with the former (stopping the task is a need to wait for a
> > > response to know what kind of exit happened. art this stage i dislike 
> > > this.
> > > i did consider having it block and wait, but decided against it. you can
> > > even see commented out code in efl_thread.c to do just this. this is the
> > > kind of thing i wanted to have discussions on in that thread/exe etc.
> > > thread. not superficial stuff like args - i added the void *'s easily
> > > enough but everyone id distracted by  things like that and not more core
> > > questions like this. should delete stop/kill a task ... and should it then
> > > wait for a response? the latter is the cleanest, but its is fragile if
> > > threads or exe's refuse to nicely exit.
> >
> > not sure you got the idea, but if you cancel a promise/future-chain
> > you get a resolution ECANCELLED automatically... there is no reason
> > you should wait it synchronously to finish, basically:
>
> no no. the stop and wait has nothing to do with promises. it has to do with
> policy - do we do this or not to get the result of the task?

IMO: no, blocking is bad.


> > exe_promise_cancel(void *data, const Eina_Promise *dead_promise EINA_UNUSED)
> > {
> >    Exe_Private_Data *pd = data;
> >    kill(pd->pid, SIGTERM);
> >    pd->promise = NULL;
> > }
> >
> > that's all... the ECANCELLED is used automatically to reject, all you
> > need to do is to invalidate the promise pointer (thus it's given as
> > parameter, if you have more than one that could be the pointer to make
> > NULL, like in a list/array).
>
> i think we're talking about different things. i was talking about what to do
> when the obj is deleted but the task hasn't exited yet. it's going to happen.
> my take is you don't wait for the task to exit. you just detach and then 
> cancel
> any pending promise internally. the "user" doesn't do this. this is a
> side-effect of deletion of the object. the promise will get cancelled so it 
> has
> a resolution of some sort.

ah, ok... then yes we agree... send signal, delete local resources and
call it done. Eventually SIGCHLD will hits the main loop and nobody
would be listening for that pid... so it will be collected and
discarded.



> > >> >> >> (and of course you are not describing it in the .eo file). I don't
> > >> >> >> even see how you can compare this two lines ? And we are not even
> > >> >> >> looking at the user of the API here which is what matter even more.
> > >> >> >> How do you make sure that the event is always delivered properly ?
> > >> >> >> How often do you generate the event (Every time someone register a
> > >> >> >> callback) ? There is a lot of open question with this kind of API
> > >> >> >> that you are just disregarding here.
> > >> >> >
> > >> >> > it's called when the parent process knows the child has exited and
> > >> >> > what the exit code is. there is no guarantee this happens during the
> > >> >> > life of the object at all. the process could become hung on a kernel
> > >> >> > syscall and never exit, ever. that's what the exit event is. when
> > >> >> > this happens. it's guaranteed to be called when this event occurs.
> > >> >> >
> > >> >> > my point is the event is 1 line. futures is a massive blob of code 
> > >> >> > to
> > >> >> > achieve the same goal. i find them basically unusable to the point
> > >> >> > that i'm just avoiding them now. if they were "1 liners" too... then
> > >> >> > great. but they are not. (well they will be a line to create and
> > >> >> > store, a line in a destructor to cancel on destruction if still 
> > >> >> > there
> > >> >> > and another line in the "where the event happens" to either call
> > >> >> > success or failure with the future data... but it needs to be far 
> > >> >> > far
> > >> >> > far simpler than it is.
> > >> >>
> > >> >> you just realize that the code is always the same, right? there is no
> > >> >> magic, we're just doing something on behalf of users, in a clear and
> > >> >> standard way.
> > >> >
> > >> > ummm it's not the same. its far longer. even if its half of what i
> > >> > described it's still far longer than an event.
> > >>
> > >> I'll not even bother to explain it's doing more on user's behalf... I
> > >> tried, Cedric tried, seems you don't want to see.
> > >
> > > i know that.. i'm not talking about the outside api to users. i'm talking
> > > about the inside api to efl to drive them.
> >
> > let's start with fixing the code? Once it's correct we can think about
> > adding some helpers. the one efl_promise_new(), that creates a promise
> > using an efl_loop_provider object as argument looks like a good one to
> > start with.
>
> so what code is needed instead of calling the exit event and the end of run()
> to create the promise? what minimal code is needed?
>
> in run()

_run(...) {
   if (pd->promise) return NULL; // do not run twice
   pd->promise = efl_loop_user_promise_new(obj, _run_cancel_cb, obj);
// efl_loop_user_promise_new() must be created, only exposed in C
   f = eina_future_new(pd->promise);
   // your code to actually run the task, if fails:
eina_future_cancel(f), return NULL;
   return efl_future_Eina_Future_XXX_then(obj, f); // will be done
automatically by eolian in the future
}

_run_cancel_cb(void *data, const Eina_Promise *dead_promise EINA_UNUSED) {
   Eo *obj = data;
   pd = efl_data_scope_get(obj...);
   pd->promise = NULL;
   efl_task_end(obj);
}


> in _exe_exit_eval()
>
>   ...(pd->promise, ...)?

_exe_exit_eval() {
    if (pd->promise) {
        if (is_error)  eina_promise_reject(pd->promise, An_Eina_Error);
        else eina_promise_resolve(pd->promise, eina_value_int_init(status));
        pd->promise = NULL;
        // calling the futures is done in the next mainloop iteration
automatically (ie: job)
    }
}


> i'll assume that destruction of the object cancels the promise, so nothing in
> destructor needed.

yes

> > >> but part of the extra work is due your misusage of the api.
> > >
> > > other storing run_future too.. i still need to store  SOME handle to this
> > > future/promise thing so i can trigger it with "it's done - here is the
> > > result".
> >
> > but flag isn't needed, job isn't needed, ref isn't needed...
>
> well flag is needed to only have run done once ever. i could have a dangling
> promise ptr indicate that too, but there is none right now. i think the ref is
> needed (see above), but job may not be needed if what you say is right - that
> they are always delayed by a job.
>
> > >> >> so yes, you're doing bit more work so users don't have to. Like
> > >> >> keeping references, canceling, etc. Our hope is that core devs (that
> > >> >> usually deal with eina_promise part) will be more careful with
> > >> >> managements and the likes.
> > >> >
> > >> > a LOT more work. and the user doesn't do less.they still have to 
> > >> > provide
> > >> > a function to call on exit.
> > >>
> > >> what?
> > >
> > > the user of the api still provide a function to be called. in fact has to
> > > provide 2 (success and failure).
> >
> > not really, this is the helper to auto-check for errors... the core is
> > only one function and you'd need to check the value (eina_error or
> > whatever you need).
> >
> >
> > >> >> users just pass one callback and *always* get called (even with
> > >> >> "ECANCELED").
> > >> >>
> > >> >> as for "lines of code", for sure we could offer a way to "add a
> > >> >> promise to this object", it would fetch the "loop" from a loop-user,
> > >> >> then fetch the scheduler... then create the promise and return.
> > >> >
> > >> > that's my point. that isn't there. and promises/futures code is just a
> > >> > hairball that figuring out how to add it is not something i want to 
> > >> > spend
> > >> > time on. i've already fixed some things there, but i have gotten to the
> > >> > point where i now am going to work around them instead. i am not even
> > >> > using the timeout futures anymore as they crash (i decided to set null
> > >> > to the content to try and catch it more easily rather than have 
> > >> > junk)...
> > >> >
> > >> > they are just not worth the pain IMHO as it stands.
> > >>
> > >> as I commented in the other PR, it's a bug somewhere else, we couldn't
> > >> see the failing code, but even with that the promise should be
> > >> protected by mempool checks, if it's crashing at least the mempool
> > >> check is buggy (since it should say the memory isn't valid).
> > >
> > > the failing code happens in non-main loop loops it seems. :) i had it 
> > > crash
> > > every single time. i gave up and used efl loop timer objects instead in my
> > > examples on the phab wiki page on exe/thread/task .... try replace them 
> > > with
> > > timeout futures and see. :)
> > >
> > >> >> the "cancel the promise" part is already done by the efl_future (note:
> > >> >> "EFL", not "EINA"), which binds the future/promise to the object
> > >> >> life... on destruction they are automatically cleared. As Cedric said,
> > >> >> once old efl_future dies and Eolian gets future<x> mapping, this will
> > >> >> be generated for you.
> > >> >
> > >> > it's not there. it's not documented that well and certainly doesn't 
> > >> > have
> > >> > the helpers needed like above, and minimal good examples that are
> > >> > concise and simple.
> > >>
> > >> ok, examples and docs may be missing indeed.
> > >
> > > so i go by what samples i can find and i base my code off them... and then
> > > as i trace them more and i have  10 then 20 lines of code i stop and go
> > > "wtf?".
> > >
> > >> > so either make futures sane and sensible to use from within efl, or 
> > >> > give
> > >> > up. i certainly am not writing a huge blob of code every time a future
> > >> > needs to be used. efl wax created originally to serve enlightenment and
> > >> > it's needs. e is in c. associated needs are too. futures do not make
> > >> > things simpler or easier there. it's some nicer syntax sugar in js
> > >> > etc. ... but this is just making code harder to use and worse for
> > >> > ourselves in the name of a currently non-existent js usersbase. if it
> > >> > came with "minimal effort beyond events" then fine. support this
> > >> > theoretical userbase and maybe it might happen. it's not minimal. that
> > >> > is my point. i don't see it moving anywhere either at this point. :(
> > >>
> > >> time to step back and try to understand what's a promise, a future,
> > >> chaining and how to properly create and use a promise.
> > >>
> > >> docs may be missing, indeed... I can't help with that right now (lack
> > >> of time), but for sure the concepts/theory part is broken in your
> > >> head... sorry :-(
> > >
> > > i know full well what they are. what i am pointing out is they are a pain 
> > > to
> > > drive from the efl side.
> >
> > if you try to drive a car like a motorcycle, you're going to have problems
> >
> >
> > > and there are bugs lurking in them to boot. hard to build anything on top.
> >
> > no doubt can exist bugs, cedric spotted one that is with a
> > not-so-common case we're trying to solve (return a resolved promise
> > from a future, not common as you'd normally just return the result,
> > not a resolved future... but still a bug).
> >
> > I can't test your example atm, not even a linux machine/vm hanging
> > around and time is short for me... but I hope others can give it a
> > try. If the bug is really on the promise/future I'll try to assign
> > someone to check it
>
> the bug i spotted was with the timeout futures... (thats why i decided to set
> things to null so i could easily know we're accessing freed memory somehow).
>
> i can change my code back into "crash mode", but i was busy working on the
> efl.exe/task/thread/appthread/app code and sample code and i just wanted
> something that worked for now and didn't want to dig into deeper issues
> that prevent it from working.

well, some time I dont touch efl, but we wrote the efl.io/net examples
using timeout futures... and was working.


-- 
Gustavo Sverzut Barbieri
--------------------------------------
Mobile: +55 (16) 99354-9890

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
enlightenment-devel mailing list
enlightenment-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/enlightenment-devel

Reply via email to