SCOOP with EiffelStudio
It's been quite some time since my last blog entry. I must confess: I abandoned Eiffel as my language of choice for a little while, to do some C++ and C#. It turns out that after I left my employer, I started working on a project of mine, with the firm intention to achieve it, make it professional but also to finish it fast. Weighting options at the beginning, I decided to avoid Eiffel to maximize the amount of reusable code I could start with, since most of it was in C++. I also avoided Eiffel for the user interface. I didn't have anything to reuse this time, but I know for a fact you cannot write WPF applications in Eiffel, and I wanted a WPF UI. Hence why I chose C#.
I'm happy to say that this project is in my opinion a success, technically speaking at least. The interested reader can visit Numbatsoft (It has nothing to do with Eiffel, though).
But the digression is over now, and I'm back, ready to code in Eiffel and talk about it again.
Well, EiffelStudio now has support for SCOOP (Simple Concurrent Object Oriented Programming). This is something I've been waiting for a long time, and naturally I was a excited at the prospect of using it.
The current implementation is of course in its early stage, and there are some issues. I'm unable to finalize code, for instance (this is for Windows 32bits). Also, standard libraries such as EiffelBase are still designed for sequential use. They can be used, but at the price of awkward class design.
I recently had a discussion with a friend, and he told me a story that can be summarized with this little exchange:
- Friend: you had sent me reports about parity errors, but recently I've stopped receiving them. What happened? How did you fix it?
- A tester: we simply turned off that exception.
- Friend: faints
Exceptions are a painful topic in programming. They never happen, except of course when unexpected and where they can cause the most damage (as Murphy told us). In the context of concurrent threads of execution, they raise interesting issues. The SCOOP documentation doesn't mention them, so I went directly at the source and read Piotr Nienaltowski's thesis, only to discover that he did not explain how to handle them either, although he identified them as a problem. With a little research I found a couple papers about it, but none seemed to give a definite solution.
After a few tests, I think I understand how exception handling has been implemented in EiffelStudio:
- After an exception, a successful retry leaves the object is a clean state. The implementation is the same as in sequential Eiffel, as it should be since this mechanism ignores the routine's environment.
- If the caller of the routine where the exception occurred is on the same processor, the exception propagates to the caller, again identically to sequential Eiffel.
- If the called is on a different processor, the entire program breaks. The behavior doesn't change if the caller has a rescue clause and is still available. Unfortunately, I could only test this in a debugging situation and not with final code.
This seems harsh, even if it helps maintaining compatibility with sequential Eiffel.
Can't we do better than that?
Programming with several threads of execution is more complicated than with just one, we all know that. In practice, programmers use synchronization APIs, ensure atomic access to resources, and handle interaction between processors in general. I think that programmers should be allowed the opportunity to react to exceptions, and be given a chance to fix the problem, since they often have control of all processors including the one where the exception occurred.
A first step would be to mark the object for which the exception occurred as broken, and postpone the actual exception handling to when it becomes necessary. For instance:
- If no other call is made to use that object, report the error at the end of the program's execution. After all, the last routine call for the broken object was a 'fire and forget' type of call, and the rest of the program decided to ignore the result.
- If a new call is made, it's time to handle the exception. This can be turned into a 'broken object' exception, and it gives the calling processor an opportunity to find a fix, and communicate this fix to other processors.
- If the 'broken object' exception is not handled, it's still time to warn the user and abort. Debugging traces can contain both the current exception, and the original one.
It is true that generating an exception on a random processor just because the object it's trying to use is broken, is unexpected. Asking routines everywhere in the code to be able to handle this kind of situation is probably too much. However, nothing prevents a programmer from doing it if the program is sufficiently modular, and if restarting all impacted threads with a fresh object is an option.
How would it work in practice? Most likely, all processors where a call to a broken object takes place would receive the 'broken object' exception, and handle it (or not). This is until no more reference to that object exists. Then it could be collected.
There are many other topics related to SCOOP I'd like to talk about: debugging tools, how processors are related to each other, interaction with C code, libraries, and more. I hope to do so as EiffelStudio gets more stable.