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. 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. anyway, last but not least `efl_future` (not EINA) is bound to an object and carries it automatically for you... so basically if the user needs that behavior, just use that helper. >> >> > 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 :-) 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. all you need is to pass "p" (the promise!) to the task resolution... once it's resolved/done, call eina_promise_resolve(). 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. >> >> > 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); >> >> >> >> This is absolutely not doing what the future code is doing. You are not >> >> detecting when a user has removed the handler and so call efl_task_end >> >> accordingly. You are also not sending a structure with the exit code >> >> either >> > >> > i don't need to send the data in a future - it's stored on the object >> > anyway. you pick it up with a get from the object. >> >> well, that's misuse of the promise result... if you're running >> something to get data, that's what you should get at the end, not the >> object... > > the object represents the execution of that task (exe) d it survives the > death of the exe or thread until its voluntarily deleted so data can be > retrieved from it "at leisure". i don't think storing this data on the object > is wrong at all because it does need to exist somewhere and storing it here is > far more convenient. it's not transient data. it doesn't keep changing like > i/o. it's stored once and stays. I think you still don't get it, what's promises meant. 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". > 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. the idea with future is the chain-ability... if you ignore that, it's almost useless as it's a sub-case of "events". >> > 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. cancelling the task = sending term/kill signals or i/o commands. >> 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. 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. >> >> (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. but part of the extra work is due your misusage of the api. >> 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? >> 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 "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 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 :-( -- 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