On Tue, 6 Mar 2018 10:37:10 -0300 Gustavo Sverzut Barbieri <barbi...@gmail.com>

> On Tue, Mar 6, 2018 at 5:40 AM, Carsten Haitzler <ras...@rasterman.com> wrote:
> > On Mon, 5 Mar 2018 11:00:27 -0300 Gustavo Sverzut Barbieri
> > <barbi...@gmail.com> said:
> >
> >> On Sat, Mar 3, 2018 at 2:02 AM, Carsten Haitzler <ras...@rasterman.com>
> >> wrote:
> >> > I just pushed:
> >> >
> >> > eb0b826776b60e0d97218242a5c285d146fb6f3b
> >> >
> >> > https://git.enlightenment.org/core/efl.git/commit/?id=1bdd9e4dd15fc27da43b50fd29bfb1b0b30ef6bd
> >> >
> >> > I wrote up a high level design document and description here for an idea
> >> > of how it works:
> >> >
> >> > https://phab.enlightenment.org/w/efl-loops-threads/
> >> >
> >> > I'm busy filling that document out a bit, but the core essentials are
> >> > there.
> >> >
> >> > There are some details like loop having an exit eina value vs task
> >> > having a simple int exit code (i'm sticking to this because it is what
> >> > processes do, it's simple and its universally supported between
> >> > processes, unlike an eina value). yes - it simplifies threads to only
> >> > having int exit codes, but the simplicity of the design is what I'm
> >> > going for. threads like executeables have full bytestream I/O for more
> >> > complex data interchange. So there probably needs to be a bit of
> >> > adjusting here and there to remove duplication. The Arguments event
> >> > delivers arguments in an array, but the task object also stores
> >> > arguments too permanently. Do we need to double-up the information? So a
> >> > few small things like this.
> >> >
> >> > I've given this design a lot of thought and what I have here I think is
> >> > clean and neat, tidy and fairly simple. It actually does work. I have
> >> > tested it of course. But please have a look and let me know what you
> >> > think. Are there some major defects in the design and idea? I know we
> >> > can expand this in future with more controls (I have no pause/resume
> >> > controls in the task interface but there should be. For processes use
> >> > SIGSTOP/CONT and for threads a co-operative request on the control
> >> > line). The internals could be better. I use pipes and this eats up a lot
> >> > of FD's for the threads where I could use socketpairs instead. I have a
> >> > separate control pipe in/out from I/O in/out where i could multiplex on
> >> > the same socketpair. Currently we run out of FD's (after about 240
> >> > threads running at once because they eat up 8 FD's per thread. The
> >> > Efl.Io code across the classes is a lot of copy & paste... And I don't
> >> > have anything to change priority of a thread once it has run (no eina
> >> > API for this - needs to be added).
> >> >
> >> > I'm not that happy though with Efl.Io and the sheer amount of code
> >> > needed to deal with it. Even as a user of the API.
> >> >
> >> > Anyway - comments, thoughts, etc. etc. ?
> >>
> >> Well, I already mentioned this to you in irc, but replying here just
> >> to make my point:
> >>
> >> I think the design is upside down, trying to make life easier at some
> >> point resulted in messy at the other side.
> >
> > how? where? how is it messy?
> >
> >> okay, call it a task, work for both process and threads, the hope is
> >> to facilitate "switch from threads to process, and vice versa", but
> >> we're getting the worst part of each it seems.
> >>
> >> ie: most thread usage is about sending shared memory back and forth,
> >> after all the benefit of threads is just that: shared memory. What we
> >> got? argv + int, useless for real life threads.
> >
> > i did this because not only does this work between processes it can work for
> > js and lua. lua can't share objects between threads. the best youcan do is a
> > luastate per thread and that pretty much means treating threads as another
> > process. the model of at the base terating threads like processes is more
> > widely applicable than "pass share objects" because several
> > languages/runtimes just can't do it at all.
> ok, for these cases, like for JS/WebWorkers the solution is to start a
> new thread passing the "script to run in that thread", in the case of
> JS/WebWorkers, there are message passing.
> That said, to fully implement something like that you better use a
> pointer (ie: string + mutex + queue), not a serialized value. Not a
> stdio-like as it was done.

stdio is a binary stream. it can be used for strings raw, or for sending ptrs
to data... it's a base transport layer. nothing more. protocols can be
implemented on top.

> >> solution? "stdio" for threads... you can send/receive I/O, done with
> >> pipes, like it would be for processes.
> >
> > it's the same thing ecore_thread does but only one way (from thread to main
> > loop) and only in a limited (only main loop can spawn threads".
> >
> > so this is a far more expanded model with data going the other way too.
> >
> >> I really, really don't like that design. To me threads should be
> >> exposed as such, with pointer argument and result... if you wish to
> >> make it easier, you could "box" it someway, like eina_value... or just
> >
> > i did the above because it's going to universally work with things like lua
> > etc.. there needs to be some kind of hook to create a new luastate in the
> > thread before executing, but then it'd work as-is.
> no, see above... you need *MORE*, unless you opt to serialize stuff as
> argv -- which is bad -- or block the thread reading from "stdin" to
> receive something -- which is also bad.

you do need something specific per language to handle launching of the thread -
e.g. as you say the actual content of a function/script to perhaps a file
path+func to call etc. BUT that doesn't change the fact that you cant just pass
void ptr's to and fro. you need to either send full scripts like stdio or use
it as a protocol transport and have protocol codec handlers like the buffered
io object.

> If you get to implement this, you'd -- on the main thread -- create a
> string (ie: script contents to run, or pre-parsed AST) + mutex +
> queues (or some "async queue" primitive we should provide). Then on
> the second thread you setup the new VM with the script contents and
> hooks that to the message passing queue, likely you need to inject in
> the VM a new function that calls back your thread, that will
> post/consume events from the queues.
> You're trying to make it easier by providing "queue as stdio", so you
> get some pre-canned notification + buffering using the kernel +
> select/poll... Not sure it would be the best for Lua (I'd rather
> notify AND then use an in-memory queue, with lower costs than
> executing "read/write" to the kernel).

that's an implementation detail that it is an fd or not. it would need an fd
anyway for the loop for wakeups. the data could be put into a side-channel
queue and just use the fd for signalling. but that's an implementation detail
hidden by the interfaces. the fd's are not exposed.

> For Python and other languages that can work with threads, you'd just
> ignore this stdio approach.

you'd probably have a protcol handler that send/received pyobject handles
somehow. but that's a layer above. the reader/writer is a raw transport layer.

> >> let the traditional "void *pointer". But that's fundamental, say for
> >> Python bindings to use threads, you want to send PyObject pointer to
> >> the thread, that object will contain the information to callback the
> >> user (the user's Python-callable). And for most usages as well, like
> >> sending file info to be opened/decoded, etc.
> >
> > i can easily add a "set a void ptr on the thread" and make it gettable from
> > the appthread. it wont work with lua though. it can't work with exe's. it's
> > kind of not that useful for python etc. because it's just a void ptr.
> Raster, maybe you should go play with bindings for a bit. None of this
> makes much sense... when you do binding you don't blindly expose every
> single call... as some calls are meant to support your binding, not to
> be exposed.

at the lower levels this is something we just don't have at all right now - the
ability to say what should and shouldn't be in which language. it's a or
nothing. these apis SHOULD be in c and c++. absolutely. any in theory should be
in D and rust too if those existed as these can all throw pointers around

> And "it can't work with exes" because it's not an interface to be
> exposed to EXE, as it's a thread thing. Read my email again, we
> shouldn't mix those, because they are different: for threads you DO
> EXPECT shared memory... that's the whole point of threads. The
> constructor should be different.

i know ... that's why the task interface is in both and has common i/o arg
passing and return passing infra. i made threads also look like exe's with args
exit code etc. to keep symmetry. threads CAN have added methods or interfaces
to do things exe's can't, but i created a common layer that they all can do.
args, exit code, stdin/out bytestreams. we have a baseline that's common. then
each specializes. they are different classes you create (EXE vs THREAD) and
thus these parent classes contain the differing features.

> >> thread I/O, while useful in some cases, I don't see why they should
> >> always exist. They should be optional, and as such they should be
> >> created explicitly.
> >
> > the control pipes do need to exist to handle joining the threads - knowing
> > when they exit (and to request) but the stdio i can make optional with
> > flags like i did with exe's. i might move those flags to the task class out
> > of the exe class then. i don't think explicit creation is necessary or even
> > good. some flags should do the trick just fine.
> I think that the pipe structure should be exposed as an Efl.Io own its
> own... then you create one if needed. Sending its read-side to one
> thread, the write side to another. It may be even useful to provide
> pipes to non-EFL entities, like external libraries (you'd send the
> pipe's fds but not use their "other end" wrapper objects)

why should someone have to go create extra objects when the single object can
do both without the added work?

for 2 non parent-child threads to communicate this would be necessary - to
create an explicit channel because they have no relationship at all other than
being in the same process. you'd nee to somehow assign a name or ID and
transport that around and then attach to it.

> I know there is an "eoid" thread access problem... that "protection"
> is showing to be more and more flawed as we start trying to do
> something useful. Not sure how you're going to solve that for
> "worker-thread-access-app" (app being in the main thread) and the
> likes, but needs to be done in a clean way, or just remove that eiod
> protection that is just causing harm.

removing the protection will raise the costs. i removed it because this was
faster and more elegant. old mails covering this. it's one tls lookup as
opposed to 2 spinlock locks + unlocks. a tls is cheaper than a single tls lock
and unlock. i put in the locks and unlocks to fix thread + eo issues where we
had no locking at all. atomics cost about the same as a spinlock lock+unlock
fyi. a little less but not much. the tls was from memory maybe 70% the cost of
a single lock + unlock so let's say it cut cost by 65% or so.

this also protects against accidental cross-thread access which is 99% of our
objects and has been a major vector for pain and bugs by users of efl that are
very hard to find and debug. so making 99% of objects less safe and slowing down
all of eo so that we can just have 1 shared parent object for app i think is a
bad trade-off.

the way the threads/app works they can communicate and do all this kind of
stuff without needing a shared top level object. each thread has its own
toplevel (efl.app or efl.appthread) ds these internally can communicate and
hide thread locking issues etc.

i think it's clean. very clean.

> >> Last but not least, I'd expose the stack just like in the OS, to not
> >> make confusion:
> >>
> >>   - Application ("the binary") -> Processes -> Threads -> main loop
> >>
> >> you can present "getters" that make it easier to access (ie: main loop
> >> "get" application, in addition to "get parent" which would return the
> >> thread).
> >
> > i already mentioned this. we can't. app object would have to be a shared
> > object. it then CANT have a non-shared child. it's a logical problem that a
> > locked object can't then modify non-locked objects (parents modify
> > children).
> >
> > but parents already have handles to child threads and can control them
> > (co-operatively though efl takes care of being co-operative).
> see, as I said above this "protection" is proving to be useless...
> just harm at no benefit.

tit's only a harm IF you want to have a single object at the top with
threads/loops as children. if you want this design - then yes, it hurts. but
see above for the downsides of having objects all be global. it raises cost of
eo calls by design and then makes accessing all those non-thread-safe objects
99% of efl more dangerous. my experience with thread-happy developers at
samsung is they violate this then push the bug back into an "efl bug" and the
bugs are hard to find and reproduce due to them being races. thread local
objects nukes that problem in one swoop. i've had to deal with this issue many
times and i've learned from the lesson.

> at first it looked a promising way to avoid mistakes, after some
> experiments it's proving to be just a PITA.

only if you insist on the design you want above. it's the only real case where
it's an issue.

> >> But mixing stuff "in the hope to make it easier" it not, it's just
> >> making things more complicated... ALSO note that the developer that
> >> would use this kind of API is "not the average developer", these don't
> >> mess with such low level primitives. The developer that is likely to
> >> use these primitives will say "what the fuck is this? I'm used to
> >> processes and threads, these guys just messed it up".
> >
> > i disagree. i've seen the use of threads pretty much because "your annual
> > bonus depends on you using more threads" and then madness that ends up as a
> > result.
> well, world is not just samsung :-D

but it is a major user of efl and if you decide to dismiss issues just because
they are not your issues, doesn't make them non-issues.

> > making threads easier to access and communicate with and deal with is
> > an important thing. hell - i want to make it easy for me too.
> >
> > ecore_thread is kind of horrible IMHO because of its major limitations in
> > this regard. this fixes many of those aspects.
> You're fixing it wrongly... as I said, thinking about efficiency this
> "channel through kernel" is horrible. And we'd be compared against the
> other technologies, all that provide some "async queue", which is
> in-memory.

it HAS to go through kernel. eina_thread_queue goes thru kernel. any kind of
system that has to wake up a loop HAS TO go through the kernel. it MUST by
definition. there is no way around that.... EXCEPT to never sleep. ever. sit in
a tight loop and poll.

i looked into zero-mq. that's how they get their massive message throughput
rates. they never sleep. the poll chewing 100% cpu. the only way to not do this
is go through kernel. you need an fd for signalling. you COULD use signals and
kill(pid) to wakeup... that is still going through the kernel. there is no way
around it. you go through the kernel at least to signal.

as i said. sending all the data is an implementation detail and it could be
done in buffers outside the pipe, but for now i put the data in the pipe to
keep it simple. it's not an api or design issue.

> >> I'm in favor of interfaces for things that are the same, so if the
> >> methods in process and threads share the same concept, behavior and
> >> parameters, make them an interface... when switching from process x
> >> threads one doesn't need to "sed" everything. However, definitely
> >> constructors are NOT the same concept, behavior or parameters, thus
> >> not part of the interface.
> >
> > to me threads and processes are the same with only a few minor differences:
> >
> > 1. a crash in a thread brings down everything, but in a process only brings
> > itself down
> > 2. threads by default share memory space (but accessing shared memory needs
> > to be carefully controlled)
> > 3. threads can't be sensibly killed off without side-effects for everyone
> > else
> this last point is not true, see the socks usage... you can declare it
> as cancellable and the SYSCALLS will fail, it's not going to abort at
> some random code path.

you have to remember to handle cleanup voluntarily. it's really nothing more
than a longjump remotely. killing a process can be done regardless if the
process wishes to voluntarily co-operate or not. that is what i'm talking
about. a thread has to co-operate. a process does not.

> so it can be controlled and "sensibly killed" without side-effects.
> > i can easily add a void * input and return in addition to args/exit code. i
> > like the args/exit code and environment being universal because if you
> > stick to this subset then things are portable between languages, runtimes
> > and between threads and processes. i can add flags to explicitly enable i/o.
> why do that instead of simply create your own I/O and pass it as the
> void* that exist for the common thread case?

then why did you complain about the args and that people will want void *'s for
threads...? why not have realized that it could have ben donm by just writing a
ptr down the stdin/out pipes? i thought that was "too much work" and so some
easier option would be needed.

> > what i haven't figured out yet or done is things like
> > ecore_thread_async_call() (and a sync version) which of course isn't going
> > to work for lua, but technically that's what the command pipe is for. i'll
> > have to make it non-blocking though to use more than it is now just for
> > exit and exited commands.
> >
> > so i'll add void ptrs for in/out in the thread class.
> >
> > i'll move i/o flags to task class from exe and use them to determine i/o
> >
> > then this covers your points i believe. in addition it still does args and
> > so on like exe's.
> why not just give it a try to isolate the pipe under efl.io.pipe and
> cleanup the thread part to be app "void*" based? Then you can pass
> it...

now i'm confused. above you say you can pass void *s via the stdin/out pipe...
now you say to pass it in as args instead of strings. which one?

args is really nothing mroe than an initial message of strings stored and
presented for you. passing in a data ptr in and out would also be no different.
it's convenience.

i disagree on having it have special separate objects for pipes. it will be
more complex for the developer because how do they deal with the object on the
other end? there needs to be a thread-agnostic way of naming/addressing such
pipes. int's strings/names/paths... then u can create a reader and a writer
for a given name/id/int/whatever. but this is more work than simply setting
in/out flags on your object and then the same reader event handling etc.
threads having these built in out of the box saves the work of having to share
id's between 2 threads somehow or come up with globally unique naming schemes
to share. the i/o on the thread itself has that already figured out with just
some flags set. (i moved to needing flags to enable this on the task class)

> later you can even provide an "async queue" object, an in-memory queue
> with proper locking and notification?

that is the i/o for the tasks class. HOW it's implemented is not that
important. it's currently actual pipes. but it'll always need a pipe or
socketpair for wakeups anwyay even if just for signalling.

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

------------- Codito, ergo sum - "I code, therefore I am" --------------
Carsten Haitzler - ras...@rasterman.com

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

Reply via email to