On Wed, Mar 14, 2018 at 2:50 AM, Carsten Haitzler <ras...@rasterman.com> wrote: > On Tue, 13 Mar 2018 10:35:15 -0300 Gustavo Sverzut Barbieri > <barbi...@gmail.com> said: > >> On Tue, Mar 13, 2018 at 1:17 AM, Carsten Haitzler <ras...@rasterman.com> >> wrote: [...] >> >> >> >> > > >> 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). >> > >> > i agree with you here as there is no action required on the loop object >> > once >> > the promise is complete. because threads and exe's require action (deletion >> > of the object otherwise you'll leak) i think it's key to pass the object >> > here, not the exit code. :) >> > >> > sure. you can pass it with data... but then the user of the api no longer >> > can pass anything else of their own as data as they used up that one value >> > for the object which is going to need addressing as above. the exit code is >> > going to be directly accessible from the passed object so no need to jump >> > through hoops to get it... :) >> > >> > at least in THIS case i think passing exit code is worse than passing >> > object. >> > >> > to boot your "i wand void pointers for threads" argument now has void ptr's >> > for in and out values for threads... and it's likely you don't want the >> > exit code but this value. i can't pass everything... so i have to choose >> > something to p[ass that is the most useful, and if i pass the object, you >> > can get the out pointer that is the "return data" from the thread. thus i >> > think this favours object being the value even more. >> >> you can pass void* as EINA_VALUE_TYPE_BLOB, the only thing is not much >> can be done about it, just release (free). > > that's far less useful than the EO obj handle... :) this can't be done for the > task class because it doesn't know about the thread superclass offering void > ptrs.
okay... I'm not going to convince you, so I'll stop trying. >> and my position still remains, even for threads... getting the actual >> thread results. > > so when a future is bound to an object - which actually most of ours seem to > be (file_set, if exe/thread get in it task class there too, jobs, timeouts), > why hide the bound object? by forcing it to be manually passed by the void > *data we use up the only user-supplied data into the result. it can't be used > by the user for their own data. because it's not relevant and shouldn't be exposed. It's how all other implementations do it... what I tried to say N times and gave up in the statement above is that you shouldn't consider the source object, just the expected output value. later, if you change from an exe to something else, say use eina_future_resolved(value) to do a mock/test... it "just works", you test the remaining pipeline without really executing the action. Not just for testing, but also to enable reuse the chain. > exe's produce int exit codes only. threads produce an int exit code AND can > deliver a void ptr as an output value as well (like ecore_thread). how do i > know which the user will want/need? the best way is to p.ass the object and > have the user fetch what they want from it. this can't be done with passing a > value because the value would be defined in the task class, not the thread > class, thus i can only pass int and nothing else. the superclass (thread) cant > go changing the type passed as the value for the future. Boom... because the efl_task interface is... wrong! As I said, the constructor is not shared (void * x argv), the exit is not shared (void * x int), the semantic is not shared (shared x isolated memory)... I said that many times. Cedric said that... but you'll not give up on your idea to abstract at the wrong layer. So what's left to do? To me it looks like it's stop discussing :-( but if you really want to keep the interface, say run() returns a promise<value>, that is, an unspecified value... people can use value.type to know what it is. >> >> 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 >> > >> > for Eo i think this would be incredibly valuable. i think it actually >> > should be the object that originated the promise (if there is an object, >> > otherwise NULL), passed there. not a void *data but actually Eo *... that'd >> > make me a happy clam and then we'd be in agreement. :) >> >> here is a clarification: usually there is no need to know the "origin" >> of something, particularly since it can be transformed in the next >> future. > > i disagree. at least for thread, exe and file_set the origin is key. that's my > point. the fact that the promise is bound to an object and that object affects > the result of the promise (success, failure) and the value (load success or > failure from file_set or return code or return data ptr from an exe or > thread)... makes it for a very different typ-e of promise than you are > thinking > promises ONLY are a value. For exe I guess I've exhausted discussing it, so giving up. As for file-set, not sure which one you're talking about. Looking at https://git.enlightenment.org/core/efl.git/tree/src/lib/efl/interfaces/efl_file.eo there is no promise, only a boolean... but say you want to do one. In the example in that interface, the promise would just deliver <void> or reject on failure. There is no value. Now thinking about use cases, while at the simplest case you may "show/enable/highlight the img object once it's loaded", for more complex cases you're operating on a *different* object, like an enclosing widget. thus, as I said, the *SOURCE* doesn't matter... what matter is your application usage. If the SOURCE was delivered as the promise result, then it would be useful for simple case. However once the application changes, the code must be changed to use the future DATA. If nothing is delivered as promise result (EINA_VALUE_EMPTY), then users would provide what they want in DATA. To change to another object just required changing the DATA given alongside your callback. >> what efl_future_..._then(obj, prev_future, cb...) does is bind the >> future to the object, so you get it when called and once it dies, your >> promise is cancelled. >> >> note that the signature is cb(obj, value), so this can be given a >> method (that accepts eina_value, returns eina_value)... >> >> the extra void *data can be added, there's no work other than adding >> the pointer to the struct and callbacks... just need to see if it's >> useful or not. >> >> maybe yes, it was not put to full practice/use yet. > > and that is when you find the errors. when it gets used. that's kind of my > point here. the design is basically missing KEY data to the result of the > promise (the object), OR i have to pass obj as the value, OR we force the user > to pass obj with the void *data thus being unable to pass something for > themselves. As I said, there it looks a good thing, the proper thing to do. however on other places: no. >> >> > > 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? >> > >> > no. it doesn't. i was mentioning that before as a policy decision/question. >> > right now it doesn't end(). >> > >> > alternatives are: >> > >> > a) it does end() and then just continue destruction >> > or >> > b) it does end() and wait for the child to exit/finish and then continue >> > destruction >> > >> > right now i opted for the current behavior. you delete... it detaches. for >> > threads this is currently a problem as there is nothing left to join the >> > thread. so "don't do this". i need to figure out a solution to this, but >> > that depends on if policy should change. for threads you can't join without >> > waiting. >> >> in the destructor if we join/wait, even if it blocks, it would make >> lives simpler, particularly for shutdown-cleanup cases (even if it's >> not the *process shutdown*, just some "close this window and bring >> everything with it"). > > it would, but at the cost of a block - possibly for a loooong time. as said there is a clean way to solve this without the block: end the task instead of destroying the object. so it's basically user's choice... we advise to call taks_end(), but if the user doesn't then we just block. >> It will block, but there is an alternative for those who want to avoid >> it: efl_task_end() BEFORE and wait the promise to be resolved... then >> delete from there. > > should the "your loop will pause - possibly for multiple seconds or even > minutes or indefinitely" be the default? it's asking for pretty serious > side-effects. if the thread or exe doesn't exit at all (it's hung), especially > with the exe as it's external, this smells of being a bit severe to me. you write in the run() and even class doc that people should either wait the future to resolve OR call task_end()/promise_cancel(). Otherwise it will block. Simple as that. secure, easy to do... >> you can write this in the class description as well as end method >> documentation. >> >> >> >> 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. >> > >> > well that's the question. so you say switch to option a) from the current >> > code. it's not unreasonable. I would need to then have the option to not do >> > this like you say. this would be necessary for enlightenment when it spawns >> > apps so when it restarts it doesn't kill off apps it started. :) but either >> > way buried in this thread is this important question and it's the kind of >> > stuff i had hoped to discuss (instead of argv stuff). >> >> well, that's because you did a very ugly design on the argv thing... >> also, instead of a constructor-only "array<string>" setter and a >> getter you replicated all array manipulation methods in the object. >> Insane :-( instead of a simple "get/set" pair written as 1 property >> you did like 5-6, that catches attention to the wrong prob. > > the array is worse IMHO. i did the array first but then when figuring out who > owns the strings and what happens if you get, modify then set... i went for > the > above instead as it just was far simpler on the user of the api. less code and > complexity for sure. think about it. copy, it's far from being an overhead compared to everything else. > have to create an array. then have to stringshare_add a new string, then > append > that, per item, then pass in as array. > > array = eina_array_new(1); > eina_array_append(array, eina_stringshare_add("arg")); > efl_task_args_set(task, array); you can add something like eina_array_from_strs("arg"), which is a macro for: eina_array_from_strv((const char *[]){__VA_ARGS__, NULL}); etc, this is useful elsewhere... also, you can even make it take an iterator, which can get a list, array, etc... (even better). > vs. > > efl_task_args_append(task, "arg"); > > likewise with modifying args members, clearing the args out, setting them > etc. ... that's another point: how common is to *MODIFY* objects of an existing task? IMHO this should even be a constructor method, not useful outside of the constructor (you can't with your approach, since you're not pre-creating the array). > the path i chose is simpler and less work in C and TBH that is our main > language and target. uglify everything because of a missing macro? >> >> > > > 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). >> > >> > indeed i'm thinking this over. the general idea is sane. should we also >> > wait >> > though? that is CLEAN in that it then gets you a return value neatly... BUT >> > it leads to side-effects of loops pausing possibly for indefinite periods. >> > i >> > dislike the block and wait... unless we can guarantee the block will be >> > very >> > short lived. >> >> as per above, on destructor I'd wait... safer. For those not wanting >> to block, just end() and wait the promise to resolve. > > if they have to wait for the promise to resolve that complicates their code if > they don't want to block and just "get out of there". they can do this end + > wait anyway to be clean, but if they are deleting the object... i think they > don't care. > > threads + outdata ptrs are a problem though because if we don't cleanly > collect > the outdata it won't get freed (there is a race where it gets sent before the > request to end but then doesn't get picked up as a reply). it's error prone to not block... if the user is not waiting. >> >> > > 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 is true... but i think the && is more common - at least for shorthand. >> > that's why the value as an object offers everything. you can get the code >> > if >> > you need to know exactly what it was. :) >> >> ok, but that's why I'm saying we should output the status(int) and >> then offer a simple future_cb that compares values, converting >> everything else to error... >> >> then you just move this decision bit further, keep your promise api >> simple, users can add a single line to check for non-zero returns. > > well isn't that the idea of having automatic decisions on failure and success > so the race/chain stuff just works? yes, but that's the thing... if you want to test for non-zero exit code? >> - >> https://git.enlightenment.org/core/efl.git/tree/src/lib/eina/eina_promise.c#n935 >> - >> https://git.enlightenment.org/core/efl.git/tree/src/lib/eina/eina_promise.c#n929 >> >> Of course these will be called in the current context/stack as there >> is no future so you can't get a scheduler to dispatch from the next >> mainloop iteration. > > interesting gotcha there. so you can't guarantee the promise is called "only > with platform code below it". :( yep, for that case (it's documented at least). >> >> > > 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). >> > >> > but the loop job is a future... created by the loop obj. not the exe. so if >> > the exe obj is deleted .. the loop job future has no idea... unless you're >> > telling me somehow it does and it assumes that void *data is actually the >> > parent owning obj of the job future... because it returns a future, not an >> > eo obj when u create loop job. so the owning object would be the loop, not >> > the exe./ if loop was deleted - then sure, but it's the exe here that is >> > the relevant object, not loop. :) >> >> you'll remove the job, use the future scheduler instead via >> eina_promise_resolve(). > > so i have to store the job future obj and basically cancel it... right? so > that's storing + cancelling on destructor (and set to null once resolved) > or... > just ref + unref in resolve :) 2 ways of dealing with it. but one way or the > other i have to deal with it. no, just the promise... if you use efl_future_..._then(), the cancel on-destructor is automatically. >> Upon eina_future_cancel(), scheduled entries are recalled/cancelled. > > yup, but have to cancel/resolve on destruction myself because i cant have the > future be a "Child object" of the eo obj and be auto deleted on parent object > destruction. :) one of those reasons why i still dislike promises not being eo > objects. no, it's automatic... they get cancelled with ECANCELED if you use efl_future_..._then() (Again: eolian will be generating the efl_future_..._then() in the future). >> remember efl_future_..._then() (EFL not EINA) binds future to the >> object, so once the object dies, pending futures are cancelled, thus >> scheduled entries are recalled... all clean, all easy :-) > > you mean efl_future_Eina_FutureXXX_then(obj, job) right? (that is a horrible > function name btw! :) ) it's because cedric didn't remove the old efl_future and we need to remove that. then: sed s/Eina_FutureXXX_//g >> https://git.enlightenment.org/core/efl.git/tree/src/lib/eina/eina_promise.h#n631 >> >> * Resolves a promise. >> * >> * This function schedules a resolve event in a >> * safe context (main loop or some platform defined safe context), >> * whenever possible the future callbacks will be dispatched. >> * >> * @param p A promise to resolve. >> * @param value The value to be delivered. Note that the value >> contents must survive this function scope, >> * that is, do @b not use stack allocated blobs, arrays, structures or >> types that >> * keep references to memory you give. Values will be automatically cleaned >> up >> * using @c eina_value_flush() once they are unused (no more future or >> futures >> * returned a new value). >> >> ;-) > > i was more thinking examples on producer side with "resolve here. note this > schedules a job in the loop to resolve it later on during the event cycle". or > something like that. i don't look at eina to know about loop scheduling stuff > normally. i look to ecore for that. i wasn't expecting eina promise to even > know about scheduling of the cb. the good part is that being in eina you solve circular dependency. Eo can't know about Efl_Loop... >> what's missing? a way to convert event->info to Eina_Value... >> currently event->info carries *NO* runtime information on its >> contents... you're left with "void *" and the developer must check in >> the docs what's in there. > > the eo files know what the info is. so there is runtime info in libeolian > actually :) that requires parsing .eo files, not feasible in runtime. >> you can have copy (value) or reference (pointers)... so say an "int", >> if passed by copy/value, changes won't be reflected outside. >> If by reference/pointers,once the callback finishes the last value >> will be reset and visible outside. > > yup. that leads to the complexity of copy or reference. if copied - how do u > copy an Eo *? if references, how do u keep the life of an int *x alive?. :) in most languages, like Swift and all, objects are ref-counted... so Eo is easy: efl_ref(). int *x is harder, but like in C++ you must keep that alive otherwise it crashes. The usual case for "reference to vars" is for synchronous lambdas, like: "foreach(cb)". So you don't need to create a context on your own (it's created implicitly). for async, like future/events you need to add the backing storage somewhere. >> >> > > 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). >> > >> > there is a big difference. http error codes are clearly defined with >> > meaning... at least exit codes of tasks are only defined as 0 == success, >> > anything else, failure. :( you still could get the exit code in the error >> > case and choose what to do... if the obj was passed - get get it and >> > decide, but for promise chains or races you have to decide at what point is >> > something a failure or success so the race/chain does the right thing. for >> > that case i think 0 == success, anything else failure is correct (cmd1 && >> > cmd2 && cmd3 ... chain like thing). if you want to explicitly decide based >> > on specific error codes you can chain etc. by hand. >> > >> > one option could be to provide an interpreter table. by default 0 == >> > success is always there. but you could add more error code values that >> > could be interpreted as success. then the task class will map those >> > specific values to success for you so the chains/races then do what you >> > want. you can always do it by hand like above too. i don't think we can by >> > default interpret like http because there is no meaning other than success >> > of failure by convention and failure reasons are undefined other than >> > "failure" in general (unlike http). >> >> see my "compare-to" above, 1 line and let the user do that. > > well they have to provide some converter func callback and then the contents > of that func. which most likely will be: > > if (code == x) || (code == y) ... etc then return success; else return fail; easier to add single value, range and set: single-value <, <=, ==, >=, >, != range in, not in set contains, not contains >> in C you can use blocks from GCC/Clang... not sure about Microsoft. > > i didn't think gcc supported blocks, just nested functions? hum... looks it's clang. didn't research much. >> worked for posix... likely to work for us... I'm a Python guy and >> exceptions do bring more data there. In JavaScript doesn't bring much >> (usually). But in Python the major use is just for pretty reporting, >> at the end you "deal with error" and that's it, eventually error >> type... not much payload. >> >> >> > if it's success the >> > value is always 0 anyway really (except see above with mapping tables). >> > it's >> > far too limited and if the object was passed then those limitations go away >> > and all that data is available. i don't want to make a "sophie's choice" as >> > to which data gets killed/dropped or harder to hunt down... the object >> > being >> > passed is my way out, but only on success. on error the eina value has to >> > be an error type thus no obj... argh. so even if the eina value was an int >> > in success, it'd be pointless for error/failure, which means everything has >> > to succeed even if "command not found" because that is also just an error >> > code (not distinguishable from linker errors or init errors or a clean >> > error exit code). >> >> Again i request you to use it a little bit... the information is >> passed elsewhere and it's what you're missing. > > i have to use the void *data to pass the obj then and i cannot use it to pass > something else of mine. i don't have to use promises to know about this > choice. > i've had to make it often enough with events where i can only pass one pointer > to one thing, and i wanted to pass more. this is always the case, particularly for simple tests... because in apps you have a "App_Ctx" that contains the whole thing (or Win_Ctx...) >> you are correct, the obj maybe useful in some cases. But stop thinking >> as "single shot big callback", this is the problem. Think "can I >> achieve this with smaller, single purpose futures?" The answer is yes. >> >> As I wrote before, your main case is "I need to unref the exe once it >> finishes executing": >> >> eina_future_chain(efl_task_run(exe), >> your_real_work_here, >> efl_future_cb_unref(exe)); // <-- this future_cb doesn't exist yet, >> we should create to make it single-line > > hmmm. that could work. if you rely on RAII in c++ and gc's in js/lua etc. sorry... why RAII? -- 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