back to API     back to index     prev     next  

Exception Handling Pattern: Handling Non-Functional Exceptions in ProActive Applications

1. What are Non-Functional Exceptions ?
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 ?

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.

  • java.io.IOException raised from sendRequest methods are always transformed into sendRequestCommunicationException
  • ProActiveException raised during migration are tranformed into serializationMigrationException
  • java.io.IOException raised during the creation of any active object are transformed into activeObjectCreationException

  • All of the distributed exceptions can be found in the following packages. Creation of new NFEs is also possible, see the "ProActive Contribution" chapter.

  • Communication exceptions : org.objectweb.proactive.core.exceptions.communication
  • Creation exceptions : org.objectweb.proactive.core.exceptions.creation
  • Group exceptions : org.objectweb.proactive.core.exceptions.group
  • Migration exceptions : org.objectweb.proactive.core.exceptions.migration
  • Security exceptions : org.objectweb.proactive.core.exceptions.security
  • Service exceptions : org.objectweb.proactive.core.exceptions.service
  •  The Handlers Mechanism

    Handlers are exception managers dedicated to non-functional exceptions. Every handler implements the following interface.

    public Interface Handler implements java.io.Serializable {
         // Is the exception managed by the current handler ?
      public boolean isHandling(NonFunctionalException e);
    
         // Handle non-functional exception(s) but still raised an exception to higher level
      public void handle(NonFunctionalException nfe, Exception e) throws Exception {
      
         // Handle non-functional exception(s)
      public void handle(NonFunctionalException e);
    }
    

    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.

  • 1. Default : This static level is initialized in the core code and provides a basic handling behaviour to every distributed exception.
  • 2. Virtual Machine : This dynamic level is used to configure a general handling behaviour in every JVM.
  • 3. Active Object or Proxy or Future : The different components of active object can be protected with specific handlers.
  • 4. Code : Handlers can be set temporarily from functional or non functional code and refer to the try/catch mechanism.
  • 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.

     Users API

    Specifications :

    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.

      public static void setExceptionHandler(HandlerClass, ExceptionClass, level, HandlerizableObject);
    

    This method binds a class of handler with a class of exception in the level specified in the parameters.

      public static Handler unsetExceptionHandler(ExceptionClass, level, HandlerizableObject);
    

    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.

    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);
    

    Level identifiers :

    This is the list of the constant used by the API to identify the different level of handling :

  • Handler.DEFAULT_LEVEL for default level
  • Handler.JVM_LEVEL for VM level
  • Handler.AO_LEVEL for Active Object level
  • Handler.PROXY_LEVEL for proxy level
  • Handler.FUTURE_LEVEL for future level
  • Handler.CODE_LEVEL for code level
  • How to customize handlers mechanism ?

    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).

     Creating a Handling Strategy : A Disconnected Mode for PDA Applications

    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 :

    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()); 
        } 
    }
    

    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.

    // 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

    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.

    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

     Dealing with Non-Functional Exceptions in ProActive Contributions

    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 :

  • 1. Define a Distributed Exception within the hierarchy of non-functional exceptions.
  • 2. Create a handler from scratch or use an existing one matching the new defined exception.
  • 3. Adapt try/catch mechanism : encapsulate exceptions with NFEs and use searchExceptionHandler.
  • 4. Register handler during initialization or execution using setExceptionHandler.
  • 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);
      }
    

     Architecture and Implementation

    The full mechanism is part of the package org.objectweb.proactive.core.exception

  • Communication exceptions : org.objectweb.proactive.core.exceptions.communication
  • Creation exceptions : org.objectweb.proactive.core.exceptions.creation
  • Group exceptions : org.objectweb.proactive.core.exceptions.group
  • Migration exceptions : org.objectweb.proactive.core.exceptions.migration
  • Security exceptions : org.objectweb.proactive.core.exceptions.security
  • Service exceptions : org.objectweb.proactive.core.exceptions.service
  • Handlers of NFEs : org.objectweb.proactive.core.exceptions.handler
  • 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

    Users API : setExceptionHandler()

     
        /**
          * 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 of NonFunctionalException.
          * @param levelID An identificator for the level where the handler is added.
          * @param target An object which contains its own level. It is null if  level is default or VM level.
          */
        public static void setExceptionHandler(Class handler, Class exception,
            int levelID, Object target) {
            // Information
            if (logger.isDebugEnabled()) {
                logger.debug("*** SETTING " + handler.getName() + " FOR " +
                    exception.getName() + " AT " + levelID + "  LEVEL");
            }
    
            // 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) {
                    //	Get the body of the target object
                    UniversalBody body = ((BodyProxy) ((org.objectweb.proactive.core.mop.StubObject) target).getProxy()).getBody();
                    try {
                        if (body instanceof ActiveBody) {
                            // Local body
                            if (logger.isDebugEnabled()) {
                                logger.debug("*** SET " + handler.getName() +
                                    " IN LOCAL BODY");
                            }
                            body.setExceptionHandler(handler, exception);
                        } else if (body instanceof RemoteBodyAdapter) {
                            // Remote body
                            if (logger.isDebugEnabled()) {
                                logger.debug("*** SET " + handler.getName() +
                                    " HANDLER IN REMOTE BODY");
                            }
                            body.getRemoteAdapter().setExceptionHandler(handler,
                                exception);
                        }
                    } catch (ProActiveException e) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("*** ERROR while SETTING handler " +
                                handler.getName() + " in BODY LEVEL");
                        }
                    }
                } else {
                    if (logger.isDebugEnabled()) {
                        logger.debug(
                            "*** ERROR: CANNOT ADD HANDLER TO NULL BODY OBJECT");
                    }
                }
                break;
            case (Handler.ID_Proxy):
                // The target object must be a proxy
                if (((target != null) && target instanceof AbstractProxy)) {
                    try {
                        ((AbstractProxy) target).setExceptionHandler(handler,
                            exception);
                    } catch (ProActiveException e) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("*** ERROR while SETTING handler " +
                                handler.getName() + " in PROXY LEVEL");
                        }
                    }
                } else {
                    if (logger.isDebugEnabled()) {
                        logger.debug(
                            "*** ERROR: CANNOT ADD HANDLER TO NULL PROXY OBJECT");
                    }
                }
                break;
            case (Handler.ID_Future):
                break;
            case (Handler.ID_Code):
                if (target != null) {
                    if (codeLevel == null) {
                        codeLevel = new HashMap();
                    }
    
                    // Try to get the hashmap associated to this object
                    if (codeLevel.containsKey(new Integer(target.hashCode()))) {
                        ((HashMap) codeLevel.get(new Integer(target.hashCode()))).put(exception,
                            handler);
                    } else {
                        codeLevel.put(new Integer(target.hashCode()), new HashMap());
                        ((HashMap) codeLevel.get(new Integer(target.hashCode()))).put(exception,
                            handler);
                    }
                } else {
                    if (logger.isDebugEnabled()) {
                        logger.debug(
                            "*** ERROR: CANNOT ADD TEMP. HANDLER TO NULL OBJECT");
                    }
                }
                break;
            }
        }
    

    Users API : unsetExceptionHandler()

    
        /**
          * 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 if  level 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
            switch (levelID) {
            // Default level must not be modified !
            case (Handler.ID_Default):
                if (logger.isDebugEnabled()) {
                    logger.debug(
                        "*** ERROR: REMOVING HANDLER from DEFAULT LEVEL is FORBIDDEN !");
                }
                return null;
            case (Handler.ID_VM):
                if (VMLevel != null) {
                    handlerClass = (Class) VMLevel.remove(exception);
                }
                break;
            case (Handler.ID_Body):
                // The target must be a body
                if (((target != null) && target instanceof UniversalBody)) {
                    //	Get the body of target (remote) object
                    UniversalBody body = ((BodyProxy) ((org.objectweb.proactive.core.mop.StubObject) target).getProxy()).getBody();
                    try {
                        if (body instanceof ActiveBody) {
                            body.unsetExceptionHandler(exception);
                        } else if (body instanceof RemoteBodyAdapter) {
                            body.getRemoteAdapter().unsetExceptionHandler(exception);
                        }
                    } catch (ProActiveException e) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("*** ERROR while UNSETTING handler for " +
                                exception.getName() + " in BODY LEVEL");
                        }
                    }
                }
                break;
            case (Handler.ID_Proxy):
                // The target must be a proxy
                if (((target != null) && target instanceof AbstractProxy)) {
                    // Create a request to associate handler to the distant body
                    try {
                        handler = ((AbstractProxy) target).unsetExceptionHandler(exception);
                        return handler;
                    } catch (ProActiveException e) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("*** ERROR while UNSETTING handler for " +
                                exception.getName() + " in PROXY LEVEL");
                        }
                    }
                }
                break;
            case (Handler.ID_Future):
                break;
            case (Handler.ID_Code):
                if ((target != null) && (codeLevel != null)) {
                    if (codeLevel.containsKey(new Integer(target.hashCode()))) {
                        handlerClass = (Class) ((HashMap) codeLevel.get(new Integer(
                                    target.hashCode()))).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;
        }
    

    Developers API : searchExceptionHandler()

    
        /**
          * 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) {
            // Temporary handler
            Handler handler = null;
    
            // Try to get a handler from object level (active object = body or proxy)
            if (target != null) {
                // Try to get an handler from code level
                if (codeLevel != null) {
                    if ((handler = searchExceptionHandler(ex.getClass(),
                                    (HashMap) codeLevel.get(
                                        new Integer(target.hashCode())),
                                    Handler.ID_Code)) != null) {
                        return handler;
                    }
                }
    
                // target is local body (i.e. active object level) ?			
                if (target instanceof ActiveBody) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("*** SEARCHING HANDLER IN LOCAL BODY");
                    }
                    try {
                        UniversalBody body = ((BodyProxy) ((org.objectweb.proactive.core.mop.StubObject) target).getProxy()).getBody();
                        HashMap map = ((UniversalBody) body).getHandlersLevel();
                        if ((handler = searchExceptionHandler(ex.getClass(), map,
                                        Handler.ID_Body)) != null) {
                            return handler;
                        }
                    } catch (ProActiveException e) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("*** ERROR : " + e.getMessage());
                        }
                    }
    
                    // target is remote body (i.e. active object level) ?
                } else if (target instanceof RemoteBodyAdapter) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("*** SEARCHING HANDLER IN REMOTE BODY");
                    }
                    try {
                        UniversalBody body = ((BodyProxy) ((org.objectweb.proactive.core.mop.StubObject) target).getProxy()).getBody();
                        HashMap map = ((UniversalBody) body).getRemoteAdapter()
                                       .getHandlersLevel();
                        if ((handler = searchExceptionHandler(ex.getClass(), map,
                                        Handler.ID_Body)) != null) {
                            return handler;
                        }
                    } catch (ProActiveException e) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("*** ERROR : " + e.getMessage());
                        }
                    }
    
                    // target is a proxy (i.e. a ref. to a body) ?
                } else if (target instanceof AbstractProxy) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("*** SEARCHING HANDLER IN PROXY");
                    }
                    try {
                        HashMap map = ((AbstractProxy) target).getHandlersLevel();
                        if ((handler = searchExceptionHandler(ex.getClass(), map,
                                        Handler.ID_Proxy)) != 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.getClass(), VMLevel,
                            Handler.ID_VM)) != null) {
                return handler;
            }
    
            // At the end, get an handler from default level or return null
            return searchExceptionHandler(ex.getClass(), 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 NFEClass Class of the non-functional exception for which a handler is searched.
          * @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(Class NFEClass, HashMap level,
            int levelID) {
            
            // Test level capacity
            if ((level == null) || level.isEmpty()) {
                return null;
            }
    
            // Retrieve an handler in the given level
            Handler handler = null;
            while ((handler == null) &&
                    (NFEClass.getName().compareTo(ProActiveException.class.getName()) != 0)) {
                // Information
                if (logger.isDebugEnabled()) {
                    logger.debug("*** SEARCHING HANDLER FOR " + NFEClass.getName() +
                        " IN LEVEL " + levelID);
                }
    
                // Research algorithm
                if (level.containsKey(NFEClass)) {
                    try {
                        handler = (Handler) Class.forName(((Class) level.get(
                                    NFEClass)).getName()).newInstance();
                    } catch (Exception e) {
                        if (e instanceof ClassNotFoundException) {
                            if (logger.isDebugEnabled()) {
                                logger.debug("*** HANDLER FOR " +
                                    NFEClass.getName() + " IS INVALID");
                            }
                            break;
                        } else {
                            e.printStackTrace();
                        }
                    }
                } else {
                    NFEClass = NFEClass.getSuperclass();
                }
            }
    
            // We return the handler
            return handler;
        }
        

    Contacts

    Any questions, comments, remarks ? Feel free to contact me here


    Copyright © October 2002 INRIA All Rights Reserved.