Using externals in multithreaded applications
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.
Using blocking qualifier
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. Consequently, 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 EiffelBase library
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:
- C/C++ externals that last a long time are usually rare.
- 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:
- One thread is taking all the CPU by looping
- All the other threads are blocked in some thread synchronization routines
- One thread is executing a C external call.
That's it, #3 is the place where you need to add the blocking qualifier.
Callbacks from C to Eiffel
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 suffix the call by the following macros
Moreover before calling the Eiffel routine, you need to ensure that no GC cycle is happening, as it is possible that while you were calling
For example, here is what one would typically have:
External threads callbacks
If your callback is performed in a thread which was not created by the Eiffel runtime through the EiffelThread library, then some more code has to be added.
The first time your external code wants to call some Eiffel code, you need to initialize the runtime for that particular thread of execution by calling the
Once it is initialized, you can perform the call back in the same manner as in the second part of the article.
After the completion of the call, you have 2 possibilities:
- Clear the data register for the current thread (meaning you know that this thread is never to execute Eiffel code again)
- Wait for a notification that the current thread is going to terminate.
In both cases, you need to manually call
To summarize your C callback routine could look like:
I hope this tutorial was helpful.
Wording
Hi Manu. Here is a quote from this tutorial: "Consequence, the GC cycle will start only". My native tongue is English and this looks incorrect to me. I would word it as the following: "Consequently, the GC cycle will start only". I don't think the comma is needed here but I do not think it is really incorrect.
Thanks, I've fixed that.