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);
    
         // Provide a treatment for handled 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 : 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) {
        	
    	// 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;
    }
    

    Developpers 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("*** 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;
       	}
    }
    

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

    Contacts

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


    Copyright © October 2002 INRIA All Rights Reserved.