Page MenuHomePhabricator

EFL interfaces, EO based Loops, Threads, Executable spawning and I/O to and from them
Updated 6 Days AgoPublic

Loops

Loops are a basic construct of any event driven system. They wait for input, then send the input to the appropriate destinations when it comes in, then go back to sleep again waiting for something to happen.

In EFL this is the Efl.Loop class. It does the looping for you and you register event callbacks with it to listen to interesting events when they happen. When they do, the callback function is called and is passed the event information. Loops will just sit and loop forever until they voluntarily quit. In the Efl.Loop setup, the first event that should happen is an Arguments event. This passes a set of arguments (an array of strings given to the Efl.Loop which may be the application arguments, or arguments provided some other way).

This Arguments callback handler should do any set-up needed for that Loop. Note that this is designed so that later MORE Arguments events may come in that may act as an extra "execution" of that loop while it is in place. For example, imagine a single terminal emulator that opens a new window for each Arguments event to have a singleton style application set-up that only has one instance of the process multiplexing every visible window to save memory.

The Loop will then start running normally, which generally involves it sleeping and waiting for some input to come in, a timeout to happen or perhaps wait until an IO output becomes available to write to with spare buffer space if there is data pending to write. It is possible to have Idler events that are continually called while the loop would otherwise be sleeping. This will continually consume CPU doing this instead of actually sleeping, so use this only if absolutely needed.

Whenever a loop is woken up, the first thing it will do is call any Idle Exiter callbacks that are registered to let the application know that an idle state has ended and we are about to process resulting events. Then all the incoming events are processed, called and any more events generated and called as needed until nothing is left to do, at which point Idle Enterers are called to indicate we're going into an idle state again, where the Loop then goes back to sleeping again.

App

Every application starts with 1 Loop that is run by the Efl.App class. This class inherits from Efl.Loop thus has the features of a Loop in addition to others. The App class exposes EFL version information as well as allowing the application to tell EFL what version is was built for as well as handling process-wide signals like USR1, USR2, HUP, TERM/QUIT/INT and process-wide pause and resume events. It also Inherits from Efl.Task that covers standard exit code handling (integer value with 0 meaning success), storing provided arguments to be able to retrieve them later as well as environment variables and priority of the task. Efl.Io is also inherited given I/O outside of the process which maps to STDIN/STDOUT.

Threads

Threads from EFL's point of view are extra lines of execution that run more loops of their own. They look just like a process tree or directory tree with parent and child relationships just like EFL's object system also does parent and child objects. There is an I/O pipeline from child to parent that is much like STDIN/STDOUT for a process.

Every Thread, like the Efl.App actually has an Efl.Appthread object created to house the thread and run the loop. Efl.Appthread also Inherits from Efl.Loop, Efl.Io and Efl.Task so it works almost the same as Efl.App but misses some of the process-global controls from Efl.App.

I/O Details

In order to create a new thread, simply create an Efl.Thread class object as a child of your Loop object somewhere, append some arguments or set the full command string, then use the Efl.Task run() method to make it start.

You will have an Efl.Thread object handle on the parent side that represents the thread that is running a new Loop. You use this object to control the thread from the parent. You can listen to I/O with the Efl.Io interface (which also is used for Efl.Exe and Efl.Net) as well as send data to it. At a basic level I/O is just a byte stream, but buffered and other I/O handling objects can be placed on top to introduce more complex protocol handling. When you want the thread to exit, ask it nicely with the end() method from Efl.Task, and the Loop running in the thread will automatically handle this request and shut down the thread object. Once the thread exits one way or another, the parent will get an Exit event and be able to get the exit code from the Efl.Thread object. At this point the parent should delete the thread object to clean up it's end. The thread can exit just like the main loop with the quit() method from the Efl.Loop class.

Exe

There is also an Efl.Exe class that inherits from Efl.Task and Efl.Io. Efl.Exe is all about spawning processes (executables) and having a control handle on them to know when they exit, or send out information to the parent process etc..

Just like Efl.Thread objects, create an Efl.Exe object type as a child of your loop, set the arguments or command string, also set the environment on this object if you want it to have any custom environment variables rather than inherit exactly what the parent has, set up an Efl.Io handling if you want I/O to and from the child process (and flags to indicate you want that I/O). Like threads, listen for the Exit event and get the exit code when the child process exits. This should make it very easy to swap in and out threads or executables for tasks as the interfaces are almost the same, and once you've mastered dealing with one, dealing with the other is dead-easy.

Tasks

Efl.Task class is meant to cover the generic idea of a task you can talk to with I/O, give initial arguments to or fetch the arguments from, manipulate some kind of environment variables etc.

It has several API's for setting the command as a full string (like a shell command line with arguments and escaping needed) and/or to set arguments 1 at a time explicitly by appending or setting by index with a string. Setting the command string or an argument by index or appending or resetting the arguments will affect each other. So it is best to choose one method and stick to it.

The environment a task exposes on anything with the Efl.Loop class actually accesses the process global environment. Efl.App loops and Efl.Appthread loops can both access this environment safely because Efl.Loop that implements this override will use locking when handling the global environment and will also duplicate strings into the local loop object to keep them safe in the local environment shadow hash. These hashes get synced when necessary to the global app environment.

Efl.Exe will inherit (copy) the environment of its parent Loop (which basically means the global environment of the app at the time), alongside any explicit modifications you make like setting specific environment variables to specific values on the Efl.Exe object. This final environment that is stored in the object will be implemented in the final executable and it's environment.


Thread Example code

This is some sample code of an app that can spawn up to N threads (the first argument provided to it must be a number like 1, 3, 10, 50). To compile:

gcc threads.c -o threads `pkg-config --cflags --libs eo eina ecore`

To run you should see something like:

$ ./threads 2
--- EFL 1.20.0
--- WRITE [0x4000000033c3] [number one] ok 16 bytes
--- WRITE [0x4000000047c8] [number one] ok 16 bytes
--- TH main 0x500000000220, 'number' 'one' indata=0x5678
--- TH main 0x500000000240, 'number' 'one' indata=0x5678
--- WRITE [0x500000002228] [number one] ok 17 bytes
--- TH READ [0x500000000220] [number one] ok 16 bytes 'hello-out-there '
--- TH WRITE [0x500000000220] [number one] ok 12 bytes
--- WRITE [0x500000002248] [number one] ok 17 bytes
--- READ [0x4000000033c3] [number one] ok 12 bytes 'yes-im-here '
--- TH READ [0x500000000240] [number one] ok 16 bytes 'hello-out-there '
--- TH WRITE [0x500000000240] [number one] ok 12 bytes
--- READ [0x4000000047c8] [number one] ok 12 bytes 'yes-im-here '
--- TH main 0x500000000240, 'number' 'two' indata=0x1234
--- TH READ [0x500000000240] [number two] ok 17 bytes 'hello-out-there2 '
--- TH WRITE [0x500000000240] [number two] ok 12 bytes
--- READ [0x500000002228] [number two] ok 12 bytes 'yes-im-here '
--- TH main 0x5000000001d1, 'number' 'two' indata=0x1234
--- TH READ [0x5000000001d1] [number two] ok 17 bytes 'hello-out-there2 '
--- TH WRITE [0x5000000001d1] [number two] ok 12 bytes
--- READ [0x500000002248] [number two] ok 12 bytes 'yes-im-here '
--- START EXIT [0x5000000001d1]
--- START EXIT [0x500000000240]
--- [0x500000002228] EXITED exit_code=99 outdata=0x9876
--- [0x500000002248] EXITED exit_code=99 outdata=0x9876
--- START EXIT [0x500000000240]
--- [0x4000000047c8] EXITED exit_code=99 outdata=0x9876
--- START EXIT [0x500000000220]
--- [0x4000000033c3] EXITED exit_code=99 outdata=0x9876

Here is the code:

threads.c
define EFL_BETA_API_SUPPORT
#include <stdio.h>
#include <string.h>

#include <Eina.h>
#include <Eo.h>
#include <Efl_Core.h>

static void _th_read_change(void *data EINA_UNUSED, const Efl_Event *ev);
static void _th_main(void *data EINA_UNUSED, const Efl_Event *ev);
static void _read_change(void *data EINA_UNUSED, const Efl_Event *ev);
static void _task_exit(void *data EINA_UNUSED, const Efl_Event *ev);

////////////////////////////////////////////////////////////////////////////
//// thread side of code
static void
_th_timeout(void *data EINA_UNUSED, const Efl_Event *ev EINA_UNUSED)
{
   Eo *obj = data;

   printf("--- START EXIT [%p]\n", obj);
   efl_threadio_outdata_set(obj, (void *)0x9876);
   efl_loop_quit(obj, eina_value_int_init(99));
}

static void
_th_read_change(void *data EINA_UNUSED, const Efl_Event *ev)
{
   // read input from parent thread/loop status chnaged - read what we can
   Eo *obj = ev->object;
   char buf[4096];
   Eina_Rw_Slice rw_slice = EINA_SLICE_ARRAY(buf);

   while (efl_io_reader_can_read_get(obj))
     {
        Eina_Error err = efl_io_reader_read(obj, &rw_slice);
        if (!err)
          {
             buf[rw_slice.len] = 0;
             printf("--- TH READ [%p] [%s] ok %i bytes '%s'\n", obj, efl_core_command_line_command_get(obj), (int)rw_slice.len, buf);

             char *buf2 = "yes-im-here ";
             Eina_Slice slice = { strlen(buf2), .mem = buf2 };
             Eina_Error err = efl_io_writer_write(obj, &slice, NULL);
             if (!err)
               {
                  Eina_Accessor *args_access = efl_core_command_line_command_access(obj);
                  printf("--- TH WRITE [%p] [%s] ok %i bytes\n", obj, efl_core_command_line_command_get(obj), (int)slice.len);
                  void *s = "";
                  eina_accessor_data_get(args_access, 1, &s);
                  if (!strcmp(s, "one"))
                    efl_add(EFL_LOOP_TIMER_CLASS, obj,
                            efl_loop_timer_interval_set(efl_added, 2.0),
                            efl_event_callback_add(efl_added, EFL_LOOP_TIMER_EVENT_TIMER_TICK, _th_timeout, obj));
                  else
                    efl_add(EFL_LOOP_TIMER_CLASS, obj,
                            efl_loop_timer_interval_set(efl_added, 1.0),
                            efl_event_callback_add(efl_added, EFL_LOOP_TIMER_EVENT_TIMER_TICK, _th_timeout, obj));
                  eina_accessor_free(args_access);
               }
          }
     }
}

static void
_th_main(void *data EINA_UNUSED, const Efl_Event *ev)
{
   // the "main function" of the thread thayt gets called with arguments
   // just like eflm_main for the main loop
   Eo *obj = ev->object;
   Eina_Accessor *args_access = efl_core_command_line_command_access(obj);
   void *s = "", *ss = "";
   eina_accessor_data_get(args_access, 0, &s);
   eina_accessor_data_get(args_access, 1, &ss);
   printf("--- TH main %p, '%s' '%s' indata=%p\n", obj, (char *)s, (char *)ss, efl_threadio_indata_get(obj));
   efl_event_callback_add
     (obj, EFL_IO_READER_EVENT_CAN_READ_CHANGED, _th_read_change, NULL);
   if (!strcmp(s, "one"))
     {
        Eina_Array *args = eina_array_new(1);
        eina_array_push(args, eina_stringshare_add("number"));
        eina_array_push(args, eina_stringshare_add("one"));
        Eo *obj2 = efl_add(EFL_THREAD_CLASS, obj,
                           efl_threadio_indata_set(efl_added, (void *)0x1234),
                           efl_core_command_line_command_array_set(efl_added, args),
                           efl_task_flags_set(efl_added, EFL_TASK_FLAGS_USE_STDOUT | EFL_TASK_FLAGS_USE_STDIN | EFL_TASK_FLAGS_EXIT_WITH_PARENT),
                           efl_event_callback_add(efl_added, EFL_LOOP_EVENT_ARGUMENTS, _th_main, NULL),
                           efl_event_callback_add(efl_added, EFL_IO_READER_EVENT_CAN_READ_CHANGED, _read_change, NULL),
                           efl_event_callback_add(efl_added, EFL_TASK_EVENT_EXIT, _task_exit, NULL),
                           efl_task_run(efl_added)
                          );

        char *buf2 = "hello-out-there2 ";
        Eina_Slice slice = { strlen(buf2), .mem = buf2 };
        Eina_Error err = efl_io_writer_write(obj2, &slice, NULL);
        if (!err) printf("--- WRITE [%p] [%s] ok %i bytes\n", obj2, efl_core_command_line_command_get(obj), (int)slice.len);
     }
   eina_accessor_free(args_access);
}

////////////////////////////////////////////////////////////////////////////
//// main loop side of code
static void
_read_change(void *data EINA_UNUSED, const Efl_Event *ev)
{
   // read output from thread status chnaged - read what we can
   Eo *obj = ev->object;
   char buf[4096];
   Eina_Rw_Slice rw_slice = EINA_SLICE_ARRAY(buf);

   while (efl_io_reader_can_read_get(obj))
     {
        Eina_Error err = efl_io_reader_read(obj, &rw_slice);
        if (!err)
          {
             buf[rw_slice.len] = 0;
             printf("--- READ [%p] [%s] ok %i bytes '%s'\n", obj, efl_core_command_line_command_get(obj), (int)rw_slice.len, buf);
          }
     }
}

static void
_task_exit(void *data EINA_UNUSED, const Efl_Event *ev)
{
   // called when the task says it has completed and exited.
   // all output to read has stopped
   Eo *obj = ev->object;
   printf("--- [%p] EXITED exit_code=%i outdata=%p\n", obj, efl_task_exit_code_get(obj), efl_threadio_outdata_get(obj));
   // thread object will be automatically deleted after as long as
   // EFL_TASK_FLAGS_EXIT_WITH_PARENT is set on task flags, and this is
   // actually the default unless you change the flags to be something
   // else. if you don't use this then the task/thread becomes orphaned
}

////////////////////////////////////////////////////////////////////////////
// just main loop input handling
static void
_stdin_read_change(void *data EINA_UNUSED, const Efl_Event *ev)
{
   // read output from thread status chnaged - read what we can
   Eo *obj = ev->object;
   char buf[4096];
   Eina_Rw_Slice rw_slice = EINA_SLICE_ARRAY(buf);

   while (efl_io_reader_can_read_get(obj))
     {
        Eina_Error err = efl_io_reader_read(obj, &rw_slice);
        if (!err)
          {
             buf[rw_slice.len] = 0;
             printf("--- STDIN READ [%p] [%s] ok %i bytes '%s'\n", obj, efl_core_command_line_command_get(obj), (int)rw_slice.len, buf);
          }
     }
}

EAPI_MAIN void
efl_main(void *data EINA_UNUSED, const Efl_Event *ev)
{
   Eo *app = ev->object;
   int threads = 2, i;
   Eina_Accessor *args_access = efl_core_command_line_command_access(app);
   void *s;

   const Efl_Version *v = efl_app_build_efl_version_get(app);
   printf("--- EFL %i.%i.%i\n", v->major, v->minor, v->micro);
   s = NULL;
   eina_accessor_data_get(args_access, 2, &s);
   if (s && (!strcmp(s, "-stdinwatch")))
     efl_event_callback_add(app, EFL_IO_READER_EVENT_CAN_READ_CHANGED,
                            _stdin_read_change, NULL);
   s = NULL;
   eina_accessor_data_get(args_access, 1, &s);
   if (s) threads = atoi(s);
   for (i = 0; i < threads; i++)
     {
        Eina_Array *args = eina_array_new(1);
        eina_array_push(args, eina_stringshare_add("number"));
        eina_array_push(args, eina_stringshare_add("one"));
        Eo *obj = efl_add(EFL_THREAD_CLASS, app,
                          efl_threadio_indata_set(efl_added, (void *)0x5678),
                          efl_core_command_line_command_array_set(efl_added, args),
                          efl_task_flags_set(efl_added, EFL_TASK_FLAGS_USE_STDOUT | EFL_TASK_FLAGS_USE_STDIN | EFL_TASK_FLAGS_EXIT_WITH_PARENT),
                          efl_event_callback_add(efl_added, EFL_LOOP_EVENT_ARGUMENTS, _th_main, NULL),
                          efl_event_callback_add(efl_added, EFL_IO_READER_EVENT_CAN_READ_CHANGED, _read_change, NULL),
                          efl_event_callback_add(efl_added, EFL_TASK_EVENT_EXIT, _task_exit, NULL),
                          efl_task_run(efl_added)
                         );

        char *buf2 = "hello-out-there ";
        Eina_Slice slice = { strlen(buf2), .mem = buf2 };
        Eina_Error err = efl_io_writer_write(obj, &slice, NULL);
        if (!err) printf("--- WRITE [%p] [%s] ok %i bytes\n", obj, efl_core_command_line_command_get(obj), (int)slice.len);
     }
}
EFL_MAIN()

Exe Example

This is some sample code of an app that runs a shell script called "./test.sh". To compile:

gcc exe.c -o exe `pkg-config --cflags --libs eo eina ecore`

To run do this and you should see the following output:

$ ./exe
--- EFL 1.20.0
--- WRITE [0x400000003374] [./loop.sh] ok 13 bytes
--- READ [0x400000003374] [./loop.sh] ok 18 bytes 'BLAH is blahvalue
'
--- READ [0x400000003374] [./loop.sh] ok 22 bytes 'INPUT is sample-input
'
--- [0x400000003374] EXITED exit_code=7

This is the test script (make sure you chmod u+x ./test.sh):

test.sh
#!/bin/sh

echo "BLAH is $BLAH"
sleep 1
read IN
sleep 1
echo "INPUT is $IN"
sleep 1
exit 7

This is the sample source code to run the test.sh script , set a special environment variable, send data to its STDIN and read its STDOUT when data comes out. Notice the similarities to dealing with threads.

exe.c
#define EFL_BETA_API_SUPPORT
#include <stdio.h>
#include <string.h>

#include <Eina.h>
#include <Eo.h>
#include <Efl_Core.h>

static void _read_change(void *data EINA_UNUSED, const Efl_Event *ev);
static void _task_exit(void *data EINA_UNUSED, const Efl_Event *ev);

static void
_read_change(void *data EINA_UNUSED, const Efl_Event *ev)
{
   // read output from exe status changed - read what we can
   Eo *obj = ev->object;
   char buf[4096];
   Eina_Rw_Slice rw_slice = EINA_SLICE_ARRAY(buf);

   while (efl_io_reader_can_read_get(obj))
     {
        Eina_Error err = efl_io_reader_read(obj, &rw_slice);
        if (!err)
          {
             buf[rw_slice.len] = 0;
             printf("--- READ [%p] [%s] ok %i bytes '%s'\n", obj, efl_core_command_line_command_get(obj), (int)rw_slice.len, buf);
          }
     }
}

static void
_task_exit(void *data EINA_UNUSED, const Efl_Event *ev)
{
   // called when the task says it has completed and exited.
   // all output to read has stopped
   Eo *obj = ev->object;
   printf("--- [%p] EXITED exit_code=%i\n", obj, efl_task_exit_code_get(obj));
   efl_loop_quit(efl_provider_find(obj, EFL_LOOP_CLASS), eina_value_int_init(99));
   // exe auto deleted at this point like efl threads. more convenient as
   // you don't need to remember to delete them yourself if launching
   // lots of commands - this is how ecore_exe worked. so listen to the
   // exit event (or del event) if you care about this... or ref it to keep
   // it around longer.
}

EAPI_MAIN void
efl_main(void *data EINA_UNUSED, const Efl_Event *ev)
{
   Eo *app = ev->object;

   const Efl_Version *v = efl_app_build_efl_version_get(app);
   printf("--- EFL %i.%i.%i\n", v->major, v->minor, v->micro);
   Eina_Array *args = eina_array_new(1);
   eina_array_push(args, eina_stringshare_add("./test.sh"));
   Eo *env = efl_duplicate(efl_env_self());
   efl_core_env_set(env, "BLAH", "blahvalue");
   Eo *obj = efl_add(EFL_EXE_CLASS, app,
                     efl_core_command_line_command_array_set(efl_added, args),
                     efl_exe_env_set(efl_added, env),
                     efl_task_flags_set(efl_added, EFL_TASK_FLAGS_USE_STDOUT | EFL_TASK_FLAGS_USE_STDIN),
                     efl_event_callback_add(efl_added, EFL_IO_READER_EVENT_CAN_READ_CHANGED, _read_change, NULL),
                     efl_event_callback_add(efl_added, EFL_TASK_EVENT_EXIT, _task_exit, NULL),
                     efl_task_run(efl_added)
                    );
   efl_unref(env);

   char *buf2 = "sample-input\n";
   Eina_Slice slice = { strlen(buf2), .mem = buf2 };
   Eina_Error err = efl_io_writer_write(obj, &slice, NULL);
   if (!err) printf("--- WRITE [%p] [%s] ok %i bytes\n", obj, efl_core_command_line_command_get(obj), (int)slice.len);
}
EFL_MAIN()
Last Author
raster
Last Edited
Tue, Sep 10, 3:09 PM
Projects
Subscribers
None