Chapter 17. Exception Handling

17.1. Exceptions and Asynchrony

In the asynchronous environment provided by ProActive, exceptions cannot be handled the same as in a sequential environment. Let's see the problem with exceptions and asynchrony in a piece of code:

 try {
     Result r = someAO.someMethodCall(); // Asynchronous method call that can throw an exception
     // ...
     doSomethingWith(r);
 }  catch (SomeException se) {
     doSomethingWithMyException(se);
 }  

In this piece of code, as the method call in line 2 is asynchronous, we don't wait for its completion and continue the execution. So, it's possible the control flow exits the try. In this case, if the method call ends up with an exception, we cannot throw it anymore back in the code because we are no more in the try block. That's why, by default, ProActive method calls with potential exceptions are handled synchronously.

17.1.1. Barriers around try blocks

The ProActive solution to this problem is to put barriers around try/catch blocks. This way, the control flow cannot exit the block, the exception can be handled in the appropriate catch block, and the call is asynchronous within the block.

With this configuration, the potential exception can be throw for several points:

  • When accessing a future

  • In the barrier

  • Using the provided API (see after)

Let's reuse the previous example to see how to use these barriers

 ProActive.tryWithCatch(SomeException.class);
 try {
       Result r = someAO.someMethodCall(); // Asynchronous method call that can throw an exception
       // ...
       doSomethingWith(r);
 ProActive.endTryWithCatch();
    } catch (SomeException se) {
       doSomethingWithMyException(se);
    } finally {
 ProActive.removeTryWithCatch();
 } 

With this code, the call in line 3 will be asynchronous, and the exception will be handled in the correct catch block. Even if this implies waiting at the end of the try block for the completion of the call.

Let's see in detail the needed modifications to the code:

  • ProActive.tryWithCatch() call right before the try block. The parameter is either the caught exception class or an array of these classes if there are many

  • ProActive.endWithTry() at the end of the try block

  • ProActive.removeTryWithCatch() at the beginning of the finally block, so the block becomes mandatory

17.1.2. TryWithCatch Annotator

These needed annotations can be seen as cumbersome, so we provide a tool to add them automatically to a given source file. It transforms the first example code in the second. Here is a sample session with the tool:

$ ProActive/scripts/unix/trywithcatch.sh MyClass.java 
--- ProActive TryWithCatch annotator -----------------------
$ diff -u MyClass.java~ MyClass.java
--- MyClass.java~
+++ MyClass.java
@@ -1,9 +1,13 @@
 public class MyClass {
     public MyClass someMethod(AnotherClass a) {
+        ProActive.tryWithCatch(AnException.class);
         try {
             return a.aMethod();
+            ProActive.endTryWithCatch();
         } catch (AnException ae) {
             return null;
+        } finally {
+            ProActive.removeTryWithCatch();
         }
     }
 }

As we can see, ProActive method calls are added to make sure all calls within try/catch blocks are handled asynchronously.

17.1.3. Additional API

We have seen the 3 methods mandatory to perform asynchronous calls with exceptions, but the complete API includes two more calls. So far, the blocks boundaries define the barries. But, some control over the barrier is provided thanks to two additional methods.

The first method is ProActive.throwArrivedException(). During a computation an exception may be raised but there is no point from where the exception can be thrown (a future or a barrier). The solution is to call the ProActive.throwArrivedException() method which simply queries ProActive to see if an exception has arrived with no opportunity of being thrown back in the user code. In this case, the exception is thrown by this method.

The method behaviour is thus dependant on the timing. That is, calling this method may or may not result in an exception being thrown, depending on the time for an exception to come back. That's why another method is provided, this is ProActive.waitForPotentialException(). Unlike the previous one, this method is blocking. After calling this method, either an exception is thrown, or it is assured that all previous calls in the block completed successfully, so no exception can be thrown from the previous calls.

17.2. Non-Functional Exceptions

17.2.1. Overview

In the first part, we were concerned with functional exception. That is, exceptions originating from 'business' code. The middleware adds its set of exceptions that we call Non-Functional Exceptions (NFE): network errors, ... ProActive has a mechanism for dealing with these exceptions.

17.2.2. Exception types

We have classified the non functional exceptions in two categories: those on the proxy, and those on the body. So, exceptions concerning the proxy are in the org.objectweb.proactive.core.exceptions.proxy package and inherits from the ProxyNonFunctionalException package.

17.2.3. Exception handlers

The NFE mechanism in ProActive calls user defined handlers when a NFE is thrown. A handler implements the following interface:

public interface NFEListener {
    public boolean handleNFE(NonFunctionalException e);
}

The handleNFE method is called with the exception to handle as parameter. The boolean return code indicates if the handler could do something with the exception. This way, if no handler could do anything with a given exception, the default behavior is used.

If the exception is on the proxy side, the default behaviour is to throw the exception which is a RuntimeException. But on the proxy side, the default behaviour is to log the exception with its stack trace to avoid killing an active object.

17.2.3.1. Association

These handlers are associated to entities generating exceptions. These are: an active object proxy, a body, a JVM. Given a NFE, the handlers on the local JVM will be executed, then either those associated to the proxy or the body depending on the exception.

Here is an example about how to add a handler to an active object on its side (body):

ProActive.addNFEListenerOnAO(myAO, new NFEListener() {
    public boolean handleNFE(NonFunctionalException nfe) {
        
// Do something with the exception...
        
// Return true if we were able to handle it
        
    return true;
    }
});

Handlers can also be added to the client side (proxy) of an active object with

 ProActive.addNFEListenerOnProxy(ao, handler) 

or to a JVM with

 ProActive.addNFEListenerOnJVM(handler) 

and even to a group with

 ProActive.addNFEListenerOnGroup(group, handler) 

These handlers can also be removed with

ProActive.removeNFEListenerOnAO(ao, handler),
ProActive.removeNFEListenerOnProxy(ao, handler),
ProActive.removeNFEListenerOnJVM(handler)
ProActive.removeNFEListenerOnGroup(group, handler)

It's also possible to define an handler only for some exception types, for example:

ProActive.addNFEListenerOnJVM(
new TypedNFEListener(
    SendRequestCommunicationException.class,
    new NFEListener() {  
         public boolean handleNFE(NonFunctionalException e) {
         // Do something with the SendRequestCommunicationException...
         // Return true if we were able to handle it
         return true;
    }
}));

You can use NFE for example, to automatically remove dead elements from a ProActive group when trying to contact them. This can be achieved using the following construction:

 ProActive.addNFEListenerOnGroup(group, FailedGroupRendezVousException.AUTO_GROUP_PURGE); 

Note that this currently works only for one-way calls.