|
![]() |
1. What are Non-Functional Exceptions ?
A few years ago, Aspect-oriented Programming defined non-functional properties (security, persistence, transaction...)
to allow the separation of concerns (for instance, see AspectJ website). Distribution can be considered
as a non-functional property especially in ProActive where distributed mechanisms are oftenly transparents. Unfortunately, exceptions related to
distribution, raised from non-functional properties (i.e. network, Java RMI or JVM), are still propagated to application code (functional level).
Non-functional exceptions signal the various failures of non-functional properties, i.e. failures related to distribution in ProActive.
Handlers of non-functional exceptions allow both middleware or application handling strategies. Nevertheless, exception are handled firstly in non-functional
property whenever it's possible. This mechanism is the result of intensive work and has been presented at EHOOS'03 (ECOOP Workshop) (article here).
As we know, non-functional exceptions (NFE) signal abnormal behaviour of non-functional properties (ie. distribution) and replace the "useless"
java.io.IOException. By the way, NFEs encapsulate RMI exceptions and add both context (i.e. JVM) and exception informations. For simplicity, they are
gathered into a hierarchy based on inheritance. This classification is also used by the handling mechanism (see below).
Figure 1 : Hierarchy of Non-Functional Exceptions for Distribution
Some failures are well-known in the RMI world. All of them have been transformed into NFEs to be relevant with the handlers mechanism. Note that java RMI
offer a larger panel of RMI exceptions than before, but unfortunately, still too limited for advanced handling strategies. The following list is not
exhaustive and should be seen as an example for any future extension to the mechanism.
All of the distributed exceptions can be found in the following packages. Creation of new NFEs is also possible, see the "ProActive Contribution" chapter.
Handlers are exception managers dedicated to non-functional exceptions. Every handler implements the
following interface.
Then, handlers are gathered into static and dynamic structures to provide prioritized levels of handling. Research of handlers starts in the highest, most specific level
and continues in lower, more generic ones as long as no handler is found. Code is the most specific level while default is the most generic.
We claim that every single NFE must be handled in a strategy defined at default level, whatever this strategy is. This recommandation offer security for
the application (no more exception lost in the code !) and avoid intensive use of dynamic levels. We recommand also that the default level provide a
middleware-oriented strategies according to the specificity of the middleware (for instance Peer to Peer, Client/Server or Desktop/Mobile applications) while
higher levels (VM, AO, proxy, future or temporarily code) use dynamic handlers (created at runtime) to improve or specialize the basic strategy.
Dynamic handlers, created or modified according to the context of the environment, offer flexibility to the strategy.
The API consists of only two static methods and is easy to learn for ProActive users. Methods can be call from functional or non functional code of the distributed applications.
This method binds a class of handler with a class of exception in the level specified in the parameters.
This method removes the class of handler associated to the given class of exception in the level specified in the parameters.
Both methods require an additional parameter call HandlerizableObject as handler of the third level (Active Object or Proxy or Future)
are associated to single object. This parameter shoudl be set to null when level is not the third one.
2. The Handlers Mechanism
3. Users API
4. Creating a Handling Strategy : A Disconnected Mode for PDA Applications
5. Performances
6. Dealing with Non-Functional Exceptions in ProActive Contributions
7. Architecture and Implementation
What are Non-Functional Exceptions ?
The Handlers Mechanism
public Interface Handler implements java.io.Serializable {
// Is the exception managed by the current handler ?
public boolean isHandling(NonFunctionalException e);
// Provide a treatment for handled exception(s)
public void handle(NonFunctionalException e);
}
Users API
Specifications :
public static void setExceptionHandler(HandlerClass, ExceptionClass, level, HandlerizableObject);
public static Handler unsetExceptionHandler(ExceptionClass, level, HandlerizableObject);
Example, protect an Active Object using active object level :
The code shows how to associate handlers to any handlerizable objects. In this case, the mobile entity is protected during migration. Until now, it is necessary to specify the body of the handlerizable object instead of the object itself but this is subject to change in the near future.
// Creation of an active object RO remote = (RO) org.objectweb.proactive.ProActive.newActive("RO"*, null, remoteNode); // * or RO.getClass().getName() // Get the remote body associated to the object (body is a meta-object representing the active object, see MOP documentation) UniversalBody ro_body = ((BodyProxy) ((org.objectweb.proactive.core.mop.StubObject) remote).getProxy()).getBody(); // If the configuration is made by the remote object itself, reference to the body must be obtained with UniversalBody ro_body = ProActive.getBodyOnThis(); // Set exception handler to the active object (i.e. to the body) // Remote object migrates now safely ProActive.setExceptionHandler(HandlerMigrationException.class, MigrationException.class, Handler.AO_LEVEL, ro_body); remote.moveTo(destinationNode); // The handler is not useful anymore ProActive.unsetExceptionHandler(MigrationException.class, Handler.AO_LEVEL, ro_body);
This is the list of the constant used by the API to identify the different level of handling :
You can overwrite NFEs and handlers of NFE. Thus, you can bind new informations to exceptions and create new handling behaviour for handlers of exceptions
(just overwrite the method public void handle(NonFunctionalException e)). If you want to change or modify the hierarchy, you should
have a look to the technical documentation (see the last part of this documentation).
Distributed applications for Personal Digital Assistants need an unconnected mode to handle errors of communication. As PDAs are mobile connections is often broken.
The main requirement is to create a specific strategy to handle communication exceptions. We create then a dedicated handler who stores requests sent to unreachable PDAs
in a queue. Time by time, a thread checks if the connection is restored in order to deliver pending requests. Here is the (simplified) code for such a handler :
Imagine a mobile object able to migrate on some wireless PDA. We just add the handler described above with the method of the API.
The mobile object is now protected and the remote method call are safe from loss of connectivity : activity of the object is stored
in the queue of pending requests and serve when the object is available again.
We did several tests showing interesting performance for the distributed applications. Overall performance remains even good with hundred handlers
despite this is a huge number of handlers for one single handlerizable object ! Handlerizable object should not have more than a few dozen especially
the mobile object. Migration performance depends greatly of the serialization time. This is why strategy attached to mobile entity should carry on
a limited set of handlers.
Creating a Handling Strategy : A Disconnected Mode for PDA Applications
Class PDAHandler_CommunicationException implements Handler {
public boolean isHandling(NonFunctionalException e) {
return (e instanceOf CommunicationException);
}
public void handle(NonFunctinalException e) {
// A thread testing connectivity is created
// The thread is also in charge to deliver pending request
if (firstUse) {
connectivityThread = new ConnectivityThread();
}
// Then reified method calls are stored in the queue and exceptions are not propagated anymore
queue.store(e.getReifiedMethodCall());
}
}
// Creation of a remote object containing handlers
RO ro = (RO) ProActive.newActive("RO", "//io.inria.fr/VM1");
// The PDA handler for communication exception is associated dynamically to the remote object
setExceptionHandler(PDAHandler_CommunicationException.class, CommunicationException.class, Handler.AO_LEVEL, ro);
// Remote method calls become sound
ro.foo();
Performances
![]() |
![]() |
![]() |
Figure 1: Comparison Between try-catch-finally and NFE Mechanism |
Figure 3: Performances of Protected Local Migration |
Figure 5: Evolution of Migration Speed, iteration after iteration |
![]() |
![]() |
![]() |
Figure 2: NFE Configuration |
Figure 4: Performances of Protected Remote Migration |
Figure 6: First Migration According to Number of Handlers |
Default handlers are statically created during ProActive initialization. They must not be removed nor overloaded at runtime because they are essential to the safety of distributed applications. Nevertheless, the standard middleware-strategy is easily customizable before compilation. Just edit ProActive.java in org.objectweb.proactive and have a look to initialization code. Add new handlers after default level creation.
// Declaration of levels of exception handling static public HashMap defaultLevel = null; static public HashMap VMLevel = null; static public HashMap codeLevel = null; // Static initialization of ProActive static { // Creation of the default level defaultLevel = new HashMap(); // Setting default handlers used at default level // VM and higher level are still set to null as long as they are empty setExceptionHandler(HandlerNonFunctionalException.class, NonFunctionalException.class, Handler.DEFAULT_LEVEL, null); setExceptionHandler(HandlerCommunicationException.class, CommunicationException.class, Handler.DEFAULT_LEVEL, null); // Other initializations follow... }
Programming new (re-programming old ?) features in ProActive requires an adaptation of the code to the mechanism. When you need new NFEs, you have to adapt the try/catch mechanism as presented in the example below. A new method is available allowing the research for a handler of NFEs : searchExceptionHandler.
// Search a handler of NFEs according to the prioritized level public static Handler searchExceptionHandler(NonFunctionalException, HandlerizableObject);
We can summarize the code adaptation to the following points :
This example shows how to customize the handlers mechanism within a proactive application. The searchExceptionHandler method search the right handler for a given exception. Keep in mind that the research starts from higher priority levels.
try { // Reified method call as founded in the core of ProActive sendRequest(methodCall, null); catch (ProActiveException e) { // Create a non functional exception encapsulating the network exception Context context = new ContextOfExecution(); NonFunctionalException nfe = new CommunicationException(e, context); // Research of the right handler for the given exception // Give reference to any active object structure to use active object levels Handler handler = searchExceptionHandler(nfe, null); handler.handle(nfe); }
The full mechanism is part of the package org.objectweb.proactive.core.exception
The NFEs inherit from the class NonFunctionalException.java (package org.objectweb.proactive.core.exceptions).
This class inherits itself from ProActiveException.java.
Handlers of NFEs inherits from Handler.java (package org.objectweb.proactive.core.exceptions.handler)
Here is the code of the three main functions of the API
/** * Search an appropriate handler for a given non functional exception. * The search starts in the highest level and continue in lower levels. When no * handler is available in one level, the search steps down into the hierarchy. * @param ex Exception for which we search a handler. * @param target An object which contains its own level. * @return A reliable handler or null if no handler is available */ public static Handler searchExceptionHandler(NonFunctionalException ex, Object target) { // an handler Handler handler = null; // Try to get an handler from code level if ((handler = searchExceptionHandler(ex, codeLevel, Handler.ID_Code)) != null) { return handler; } // Try to get a handler from object level (active object, future, proxy) // The target object must be identified if ((target != null) && target instanceof UniversalBody) { try { HashMap map= ((UniversalBody) target).getHandlersLevel(); if ((handler = searchExceptionHandler(ex, map, Handler.ID_Body)) != null) { return handler; } } catch (ProActiveException e) { if (logger.isDebugEnabled()) { logger.debug("*** ERROR : " + e.getMessage()); } } } // Try to get an handler from VM level if ((handler = searchExceptionHandler(ex, VMLevel, Handler.ID_VM)) != null) { return handler; } // Try to get an handler from default level or return null return searchExceptionHandler(ex, defaultLevel, Handler.ID_Default); } /** * Search an appropriate handler for a given non functional exception. * We first search in the highest level a handler for the real class of the exception. If the search fails, we try * with mother classes. When no handler is available in this level, we go down into the hierarchy of levels. * @param ex Exception for which we search a handler. * @param level The level where handlers are searched * @param levelID Identificator of the level * @return A reliable handler or null if no handler is available */ public static Handler searchExceptionHandler(NonFunctionalException ex, HashMap level, int levelID) { // Test the capacity of the level if ((level == null) || level.isEmpty()) { return null; } // We get the class of the raised exception Class exClass = ex.getClass(); Handler handler = null; // Then we search if a handler exists in the given level // We stop when we find either right handler or none while ((handler == null) && (exClass.getName().compareTo(ProActiveException.class.getName()) != 0)) { // Information if (logger.isDebugEnabled()) { logger.debug("*** SEARCHING HANDLER FOR " + exClass.getName() + " IN LEVEL " + levelID);" IN LEVEL " + levelID); } // Research algorithm if (level.containsKey(exClass)) { try { handler = (Handler) Class.forName(((Class) level.get(exClass)).getName()).newInstance(); } catch (Exception e) { if (e instanceof ClassNotFoundException) { if (logger.isDebugEnabled()) { logger.debug("*** HANDLER FOR " + exClass.getName() + " IS INVALID"); } break; } else { e.printStackTrace(); } } } else { exClass = exClass.getSuperclass(); } } // We return the handler return handler; }
/** * Add one handler of Non Functional Exception (nfe) to a specific level. * Similar handlers are overwritten except those at default level. * @param handler A class of handler associated with a class of non functional exception. * @param exception A class of non functional exception. It is a subclass ofNonFunctionalException
. * @param levelID An identificator for the level where the handler is added. * @param target An object which contains its own level. It is null iflevel
is default or VM level. */ public static void setExceptionHandler(Class handler, Class exception, int levelID, Object target) { // Information if (logger.isDebugEnabled()) { logger.debug("*** SET [" + handler.getName() + "] FOR [" +exception.getName() + "] AT LEVEL " + levelID + " "); } // To minimize overall cost, level are created during the association of the first handler switch (levelID) { case (Handler.ID_Default): if (defaultLevel.get(exception) == null) { defaultLevel.put(exception, handler); } break; case (Handler.ID_VM): if (VMLevel == null) { VMLevel = new HashMap(); } VMLevel.put(exception, handler); break; case (Handler.ID_Body): // The target object must be a body if ((target != null && target instanceof org.objectweb.proactive.core.body.UniversalBody)) { // Information about the body //System.out.println("[CLASS " + ((UniversalBody) target).getClass().getName() + "] on [" + ((UniversalBody) target).getNodeURL() + "] is PROTECTED with [" + handler.getName() + "] at LEVEL " + levelID); // Create a request to associate handler to the distant body try { if (target instanceof ActiveBody) { ((UniversalBody) target).setExceptionHandler(handler, exception); } else if (target instanceof RemoteBodyAdapter) { UniversalBody body = ((UniversalBody) target).getRemoteAdapter(); body.setExceptionHandler(handler, exception); } } catch (ProActiveException e) { if (logger.isDebugEnabled()) { logger.debug("*** ERROR : SET handler " +e + " in BODY");} } } break; case (Handler.ID_Proxy): case (Handler.ID_Future): break; case (Handler.ID_Code): if (codeLevel == null) { codeLevel = new HashMap(); } codeLevel.put(exception, handler); break; } }
/** * Remove a handler associated to a class of non functional exceptions. * @param exception A class of non functional exception which does not require the given handler anymore. * @param levelID An identificator for the level where the handler is removed. * @param target An object which contains its own level. It is null iflevel
is default or VM level. */ public static Handler unsetExceptionHandler(Class exception, int levelID, Object target) { // We keep a trace of the removed handler Class handlerClass = null; Handler handler = null; // The correct level is identified HashMap level = null; switch (levelID) { // Default level must not be modified ! case (Handler.ID_Default): if (logger.isDebugEnabled()) { logger.debug("*** CAN'T REMOVE ANY HANDLER from DEFAULT LEVEL !"); } return null; break; case (Handler.ID_VM): if (VMLevel != null) { handlerClass = (Class) level.remove(exception); } break; case (Handler.ID_Body): // The target object must be a body if ((target != null && target instanceof org.objectweb.proactive.core.body.UniversalBody)) { // Create a request to associate handler to the distant body try { if (target instanceof ActiveBody) { handler = ((UniversalBody) target).unsetExceptionHandler(exception); return handler; } else if (target instanceof RemoteBodyAdapter) { UniversalBody body = ((UniversalBody) target).getRemoteAdapter(); handler = body.unsetExceptionHandler(exception); return handler; } } catch (ProActiveException e) { if (logger.isDebugEnabled()) { logger.debug("*** ERROR to UNSET handler in BODY : " + e);} } } case (Handler.ID_Proxy): case (Handler.ID_Future): break; case (Handler.ID_Code): if (codeLevel != null) { handlerClass = (Class) level.remove(exception); } break; } // Instantiation of the removed handler if (handlerClass != null) { try { handler = (Handler) handlerClass.newInstance(); if (logger.isDebugEnabled()) { logger.debug("*** REMOVE [" + handler.getClass().getName() + "] FOR [" + exception.getName() + "] AT LEVEL " + levelID); } } catch (Exception e) { if (logger.isDebugEnabled()) { logger.debug("*** ERROR during class [" + handlerClass.getName() + "] instantiation"); } } } return handler; }
Any questions, comments, remarks ? Feel free to contact me here