FileDocCategorySizeDatePackage
CleanShutdownInterceptor.javaAPI DocJBoss 4.2.116714Fri Jul 13 20:52:34 BST 2007org.jboss.ejb.plugins

CleanShutdownInterceptor

public class CleanShutdownInterceptor extends AbstractInterceptor
Track the incoming invocations and when shuting down a container (stop or destroy), waits for current invocations to finish before returning the stop or destroy call. This interceptor can be important in clustered environment where shuting down a node doesn't necessarly mean that an application cannot be reached: other nodes may still be servicing. Consequently, it is important to have a clean shutdown to keep a coherent behaviour cluster-wide. To avoid strange or inefficient behaviour, the facade session bean (if any) should be stopped first thus not blocking invocations in a middle-step (i.e. facade making multiple invocations to "sub-beans": if a "sub-bean" is shut down, the facade will get an exception in the middle of its activity)
see
author
Sacha Labourey.
version
$Revision: 57188 $

Revisions:

14 avril 2002 Sacha Labourey:

  • First implementation

Fields Summary
protected org.jboss.ejb.Container
container
protected org.jboss.ejb.EjbModule
ejbModule
protected String
ejbModuleName
private static ThreadLocal
currentModule
protected boolean
allowInvocations
protected boolean
allowRemoteInvocations
protected boolean
isDebugEnabled
public long
runningInvocations
public long
runningHomeInvocations
public long
shutdownTimeout
public long
readAcquireTimeMs
protected EDU.oswego.cs.dl.util.concurrent.ReentrantWriterPreferenceReadWriteLock
rwLock
private static final String
METHOD_INVOCATION_TAG
Constructors Summary
public CleanShutdownInterceptor()


   // Constructors --------------------------------------------------

     
   
   
Methods Summary
protected voidcontainerIsAboutToStop()

      log.debug ("Container about to stop: disabling HA-RMI access to bean from interceptor");
      
      // This is bad: we should have some kind of code (manager) associated
      // with this ejbModule. We mimic this by electing the first ProxyFactoryHA
      // as a manager
      //
      boolean iAmTheManager = !Boolean.TRUE.equals (ejbModule.getModuleData ("ShutdownInterceptorElected"));
      
      if (iAmTheManager)
      {
         ejbModule.putModuleData ("ShutdownInterceptorElected", Boolean.TRUE);
         
         if (isDebugEnabled) log.debug ("Container is about to stop and I am the manager of the first step: blocking remote calls");
         // in a first step, all interceptors must refuse/redirect remote invocations
         //
         Collection containers = ejbModule.getContainers ();
         Iterator containersIter = containers.iterator ();
         while (containersIter.hasNext ())
         {
            Container otherContainer = (Container)containersIter.next ();
            CleanShutdownInterceptor inter = (CleanShutdownInterceptor)
               ejbModule.getModuleData ("CleanShutDownInterceptor-" + otherContainer.getServiceName ().toString ());
            if (inter == null)
            {
               log.debug ("Found an EJB that doesnt have a clean-shutdown interceptor: " + otherContainer.getJmxName ());
            }
            else
            {
               inter.onlyAllowLocalInvocations ();
            }
         }
      }
      else
      {
         if (isDebugEnabled) log.debug ("Container is about to stop but I am not the manager: I don't manage the first step of the process.");
      }
      
      // in a second step, all container, manager or not, will wait that no more invocation
      // are running through
      // The cycling around other interceptor is managed by the JMX callbacks, not by us
      //
      waitForNoMoreInvocations ();
   
public voidcreate()

      super.create ();
      this.allowInvocations = false;
      this.allowRemoteInvocations = false;
      
      this.isDebugEnabled = log.isDebugEnabled ();
      
      ejbModuleName = ejbModule.getServiceName().toString();
      
      // we register our inner-class to retrieve STATE notifications from our container
      //
      AttributeChangeNotificationFilter filter = new AttributeChangeNotificationFilter ();
      filter.enableAttribute ("State");
      
      this.container.getServer ().
         addNotificationListener (this.container.getEjbModule ().getServiceName (), 
                                  new CleanShutdownInterceptor.StateChangeListener (), 
                                  filter, 
                                  null);

      // we need a way to find all CleanShutDownInterceptor of an EjbModule
      //
      ejbModule.putModuleData ("CleanShutDownInterceptor-" + this.container.getServiceName ().toString (), this);
   
public voiddestroy()

      super.destroy ();

      this.log.debug ("Destroying container " + container.getJmxName ().toString () + ". " +
                        this.runningHomeInvocations + " current home invocations and " +
                        this.runningInvocations + " current remote invocations.");

      forbidInvocations() ;
   
protected voidforbidInvocations()

      this.allowInvocations = false;

      purgeRunningInvocations();
   
public org.jboss.ejb.ContainergetContainer()

      return this.container;
   
protected java.lang.StringgetOrigin(org.jboss.invocation.Invocation mi)

      String value = (String)currentModule.get ();
      if (log.isTraceEnabled())
         log.trace ("GET_ORIGIN: " + value + " in " + this.container.getServiceName ().toString ());
      return value; 
   
public java.lang.Objectinvoke(org.jboss.invocation.Invocation mi)

      if (this.allowInvocations)
      {
         String origin = getOrigin (mi);
         boolean isAppLocalCall = ejbModuleName.equals (origin);
         
         if (!this.allowRemoteInvocations && !isAppLocalCall)
            // it is a remote call and they are currently forbidden!
            //
         {
            if (isDebugEnabled) log.debug ("Refusing a remote invocation");
            throw new GenericClusteringException (GenericClusteringException.COMPLETED_NO,
                                                  "This application does not accept remote calls any more");
         }

         // we need to acquire the read lock. If we cannot directly, it means
         // that the stop/destroy call has gotten the write lock in the meantime
         //
         try
         {
            if (!isAppLocalCall) // we only consider remote calls => every local originates from a remote!
            {
               if (!rwLock.readLock ().attempt (readAcquireTimeMs))
                  throw new GenericClusteringException (GenericClusteringException.COMPLETED_NO,
                                                        "Container is shuting down on this node (timeout)");
            }
         }
         catch (java.lang.InterruptedException ie)
         {
            throw new GenericClusteringException (GenericClusteringException.COMPLETED_NO,
                                                  "Container is shuting down on this node");
         }

         runningInvocations++;
         try
         {
            if (!isAppLocalCall)
               setOrigin (mi);
            return this.getNext ().invoke (mi);
         }
         catch (GenericClusteringException gce)
         {
            // a gce exception has be thrown somewhere else: we need to modify its flag
            // and forward it. We could add optimisations at this level by having some
            // "idempotent" flag at the container level
            //
            gce.setCompletionStatus (gce.COMPLETED_MAYBE);
            throw gce;
         }
         finally
         {
            if (!isAppLocalCall)
               revertOrigin (mi, origin);

            runningInvocations--;
            if (!isAppLocalCall) // we only consider remote calls => every local originates from a remote!
               rwLock.readLock ().release ();
         }
      }
      else
         throw new GenericClusteringException (GenericClusteringException.COMPLETED_NO,
                                               "Container is shuting down on this node");
   
public java.lang.ObjectinvokeHome(org.jboss.invocation.Invocation mi)

      if (this.allowInvocations)
      {
         String origin = getOrigin (mi);
         boolean isAppLocalCall = ejbModuleName.equals (origin);
         
         if (!this.allowRemoteInvocations && !isAppLocalCall)
            // it is a remote call and they are currently forbidden!
            //
         {
            if (isDebugEnabled) log.debug ("Refusing a remote home invocation. here= " + ejbModuleName + "; Origin= " + origin);
            throw new GenericClusteringException (GenericClusteringException.COMPLETED_NO,
                                                  "This application does not accept remote calls any more");
         }
         
         // we need to acquire the read lock. If we cannot directly, it means
         // that the stop/destroy call has gotten the write lock in the meantime
         //
         try
         {
            if (!isAppLocalCall) // we only consider remote calls => every local originates from a remote!
            {
               if (!rwLock.readLock ().attempt (readAcquireTimeMs))
                  throw new GenericClusteringException (GenericClusteringException.COMPLETED_NO,
                                                        "Container is shuting down on this node (timeout)");
            }
         }
         catch (java.lang.InterruptedException ie)
         {
            throw new GenericClusteringException (GenericClusteringException.COMPLETED_NO,
                                                  "Container is shuting down on this node");
         }

         runningHomeInvocations++;
         try
         {
            if (!isAppLocalCall)
               setOrigin (mi);
            return this.getNext ().invokeHome (mi);
         }
         catch (GenericClusteringException gce)
         {
            // a gce exception has be thrown somewhere else: we need to modify its flag
            // and forward it. We could add optimisations at this level by having some
            // "idempotent" flag at the container level
            //
            gce.setCompletionStatus (gce.COMPLETED_MAYBE);
            throw gce;
         }
         finally
         {
            if (!isAppLocalCall)
               revertOrigin (mi, origin);
            
            runningHomeInvocations--;
            if (!isAppLocalCall) // we only consider remote calls => every local originates from a remote!
               rwLock.readLock ().release ();
         }
      }
      else
         throw new GenericClusteringException (GenericClusteringException.COMPLETED_NO,
                                               "Container is shuting down on this node");
   
public voidonlyAllowLocalInvocations()

      if (isDebugEnabled) log.debug ("Only allow local invocation from now on: " + this.container.getServiceName ().toString ());
      this.allowRemoteInvocations = false;
   
protected voidpurgeRunningInvocations()

      try
      {
         if (this.rwLock.writeLock ().attempt (shutdownTimeout))
            this.rwLock.writeLock ().release ();
         else
            log.info ("Possible running invocations not terminated " + 
               "while leaving the container. Home: " + runningHomeInvocations +
               ". Remote: " + runningInvocations + ".");         
      }
      catch (Exception e) 
      {
         log.info ("Exception while waiting for running invocations " + 
            "to leave container. Home: " + runningHomeInvocations +
            ". Remote: " + runningInvocations + ".", e);         
      }
      finally
      {
         
      }
   
protected voidrevertOrigin(org.jboss.invocation.Invocation mi, java.lang.String origin)

      
      if (log.isTraceEnabled()) log.trace ("Crossing ejbModule border from " + this.ejbModuleName + " to " + origin);
      currentModule.set (origin);
   
public voidsetContainer(org.jboss.ejb.Container con)
This callback is set by the container so that the plugin may access it

param
con The container using this plugin.

      this.container = con;
      if (con != null)
         this.ejbModule = con.getEjbModule ();
      else
         this.ejbModule = null;
   
protected voidsetOrigin(org.jboss.invocation.Invocation mi)

      
      currentModule.set (this.ejbModuleName);
   
public voidstart()

      super.start();
      this.allowInvocations = true;
      this.allowRemoteInvocations = true;
   
public voidstop()

      super.stop ();

      this.log.debug ("Stopping container " + container.getJmxName () + ". " +
                        this.runningHomeInvocations + " current home invocations and " +
                        this.runningInvocations + " current remote invocations.");

      forbidInvocations ();
   
public voidwaitForNoMoreInvocations()

      this.log.debug ("Waiting that the container " + container.getJmxName () + " finishes its running invocations. " +
                        this.runningHomeInvocations + " current home invocations and " +
                        this.runningInvocations + " current remote invocations.");

      purgeRunningInvocations ();
      if (isDebugEnabled) log.debug ("... Done: no more remote invocations currently running in this container.");