eiffelroom

articleUsing externals in multithreaded applications

manus_eiffel's picture

Using a C/C++ external in an Eiffel system is pretty easy. Simply do a C/C++ inline and provide the arguments to the C/C++ externals.

When transforming the same system into a multithreaded application one has to pay attention to something else: C/C++ externals that may take a long time to execute.

The issue is specific to the Eiffel Software GC implementation which is a stop the world type of GC. In other words, when a GC cycle is triggered, all the running threads have to be stopped. However, when a thread is executing an external C/C++ code, the Eiffel runtime has no way to stop that thread reliably on all the supported platforms. Consequence, the GC cycle will start only after the external C/C++ code has completed and this could cause an unacceptable delay.

The adopted solution was to add a new qualifier to C/C++ externals: blocking. This keywords directly follows to the C or C++ keyword in the external specification. Here is an example taken from the EiffelThread library {THREAD_CONTROL}.sleep:

sleep (nanoseconds: INTEGER_64) is
        -- Suspend thread execution for interval specified in
        -- `nanoseconds' (1 nanosecond = 10^(-9) second).
    require
        non_negative_nanoseconds: nanoseconds >= 0
    external
        "C blocking use %"eif_threads.h%""
    alias
        "eif_thr_sleep"
    end

Since it is cumbersome to have to go through all the externals and add the missing blocking qualifier, one may ask, why it isn't the default behavior. There are 2 elements of response:

  1. C/C++ externals that last a long time are usually rare.
  2. Making a C/C++ externals blocking by default would potentially cause race conditions (thus random results/crashes) when the C/C++ externals call back to Eiffel code.

This is really point #2 that made us choose the current described solution of adding the blocking qualifier wherever it was needed, since when you have to choose between a deadlock and a crash, the deadlock alternative is much easier to debug.

To figure out when you need to put a blocking qualifier, simply execute your program and when it seems to freeze, look at the call stacks for the various threads in a C debugger. Usually, the scenario is the following:

  1. One thread is taking all the CPU by looping
  2. All the other threads are blocked in some thread synchronization routines
  3. One thread is executing a C external call.

That's it, #3 is the place where you need to add the blocking qualifier.

Now, let's tackle a more complicated scenario where you have a C/C++ external calling some Eiffel code. Here is what you need to do. First, the C/C++ externals needs to be marked blocking, then in the C/C++ code where you do a call back to the Eiffel code, you need to prefix and suffic the call by the following macros EIF_ENTER_EIFFEL and EIF_EXIT_EIFFEL. For example, here is what one would typically have:

my_c_routine is
    external
        "C blocking use %"some_header_file.h%""
    end

void my_c_routine (void) {
    /* Some C code here. */

    EIF_ENTER_EIFFEL;
    your_eiffel_routine_call (eif_access(my_object), ....);
    EIF_EXIT_EIFFEL;

    /* Some other C code here. */
}

I hope this tutorial was helpful.

about - contact