SCOOP with EiffelStudio

by David Le Bansais (modified: 2011 Apr 25)

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.

SCOOP

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.

Exceptions

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.

More SCOOP

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.

Comments
  • Helmut Brandl (6 years ago 27/4/2011)

    SCOOP and exceptions

    Hello David, I am happy that you are back to Eiffel because I apreciate a lot your posts and your ideas (immutability etc.). I hope you will continue.

    I agree with you that exceptions are not handled properly within the current version of SCOOP. Beside Pjotr's thesis there are some other publications trying develop a solution. But to the best of my knowledge nobody has yet come up with a satisfactory solution.

    My comment: The unresolved exception problem within ise-scoop is just a symptom and not the real cause of the problem (like catcalls are just a symptom which point to a design flaw in the inheritance concept). In my opinion the real cause is ise-scoop's handling of synchronization (wait by necessity). ise-scoop allows that a separate call runs unsynchronized with the caller. It treats separate procedure calls as message sending (i.e. requests which are queued into the request queue of the called process) and the caller can continue with its work as long as it does not need any results from the call. This inevitably leads to the problem you have indicated. If during the separate call an exception is raised and the called process does not handle it, then there is no way to handle this exception. All already suggested solutions which I know of just try to work around this core problem (e.g. dispatch the exeption to the next caller of the process or block all calls of other processes until the original caller makes another call and dispatch the exception to the original caller).

    The solution: Separate calls must properly synchronize the calling and called process. This does not exclude wait by necessity. But the compiler must make sure that a separate call synchronizes the calling and called process(es) logically. Wait by necessity is an optimization option for the compiler if it can prove that no exeption can be raised during the call and the caller does not need the results of the call.

    • Manu (6 years ago 27/4/2011)

      I don't understand your solution. What the point of synchronizing the calls? This is effectively killing the concurrency.

      I'd say that reasoning about concurrency and SCOOP should be done first without wait by necessity which happens only with contracts. Do the thinking of concurrency first assuming that all contracts hold, and then see what you should do with them as a second step.

      In the current SCOOP implementation, although not yet implemented that way, we made it possible to have calls on a different processor without blocking the current processor to reserve resources on the other processors. This is necessary to have a highly concurrent system.

      For exceptions there are various solutions which are possible, the one I prefer is to raise the exception next time you try to apply a call on a dirty object/processor, but also have an exception manager that receives the exception whenever one occurs in the system, that way you can have a global monitoring of exceptions. Currently EiffelStudio does not implement exceptions support in our SCOOP implementation since the solution hasn't been fully drafted yet.

      • David Le Bansais (6 years ago 27/4/2011)

        For exceptions there are

        For exceptions there are various solutions which are possible, the one I prefer is to raise the exception next time you try to apply a call on a dirty object/processor.

        This sounds like what I described. However, in my opinion, the exception should be raised every time you try to apply a call to the object, not the next time. Also, only the object should be 'dirty', not the processor.

        EDIT: the exception should be raised every time you try to apply a call to the object, not the next time. Actually, I take that back, as the issue is more complicated than that (big surprise!)

      • Helmut Brandl (6 years ago 27/4/2011)

        don't understand?

        How can you say that this is killing concurrency if you don't understand it?

        Synchronization is not killing concurrency. If you have a couple of processes (or processors in your terminology) which don't interact you have full concurrency. If you want interaction you need to synchronize in some manner. I have raised the point that your synchronization concept is not yet fully mature. The proposed workarounds (like the one you proposed) might help to get programs which work as expected by the designer. So it is good to invent some workarounds. But it is also worthwhile to solve the deeper core problem.

        I have not said that the solution I have proposed is the best one. But it might deserve some discussion and not just turning it down by the remark "This is effectively killing concurrency." (which is not true).

        • David Le Bansais (6 years ago 27/4/2011)

          I read you carefully, and what you're suggesting is that if object A on processor P1 is applying a call to object B on processor P2, and the call raises an exception, the solution is a mechanism to report the exception from P2 to P1.

          I could say that P1 might no longer exist, but I guess there can be the equivalent of a call stack for processors, so, ultimately there is a processor available to receive the exception.

          However, that processor might be running a completely different set of objects at that time. So again, I guess you mean the stack of object calls within the stack of involved processors (I know I'm not clear, but sample code would take a lot of room). Anyway, when the exception happens, on the processor that is capable of receiving the exception, you could trigger a new exception in a routine that is member of the call stack.

          The objection in that case if that if there was a rescue clause in a routine deeper in the stack, this rescue clause had no chance to fix the problem since it happened after. This breaks the concept of escalating the exception until it can be handled.