[jboss-cvs] JBossAS SVN: r83968 - in branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3: src/main/org/jboss/ejb3/cache/simple and 7 other directories.

jboss-cvs-commits at lists.jboss.org jboss-cvs-commits at lists.jboss.org
Fri Feb 6 11:44:39 EST 2009


Author: galder.zamarreno at jboss.com
Date: 2009-02-06 11:44:38 -0500 (Fri, 06 Feb 2009)
New Revision: 83968

Added:
   branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/resources/test/ejbthree1549/
   branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/resources/test/ejbthree1549/ear/
   branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/resources/test/ejbthree1549/ear/META-INF/
   branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/resources/test/ejbthree1549/ear/META-INF/application.xml
   branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/resources/test/ejbthree1549/jar/
   branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/
   branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/BlockingPersistenceManager.java
   branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/ContainerAccessInterceptor.java
   branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/ForceEventsCache.java
   branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/MyStatefulBean.java
   branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/MyStatefulCacheProxyBean.java
   branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/MyStatefulCacheProxyRemote.java
   branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/MyStatefulLocal.java
   branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/unit/
Modified:
   branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/build-test.xml
   branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/main/org/jboss/ejb3/cache/simple/SimpleStatefulCache.java
   branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/main/org/jboss/ejb3/stateful/StatefulLocalProxy.java
Log:
[JBPAPP-1671] Reduced locking in SimpleStatefulCache to allow invocations and new sessions to continue as normal.

Modified: branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/build-test.xml
===================================================================
--- branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/build-test.xml	2009-02-06 16:29:58 UTC (rev 83967)
+++ branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/build-test.xml	2009-02-06 16:44:38 UTC (rev 83968)
@@ -1896,6 +1896,28 @@
       </copy>
    </target>
    
+   <target name="ejbthree1549"
+      description="Builds a simple ear file."
+      depends="compile-classes">
+      
+      <jar jarfile="${build.lib}/ejbthree1549.jar">
+         <fileset dir="${build.classes}">
+            <include name="org/jboss/ejb3/test/ejbthree1549/*.class"/>
+         </fileset>
+         <fileset dir="${resources}/test/ejbthree1549/jar"/>
+      </jar>
+      
+      <jar jarfile="${build.lib}/ejbthree1549.ear">
+         <fileset dir="${build.lib}">
+            <include name="ejbthree1549.jar"/>
+         </fileset>
+         <fileset dir="${resources}/test/ejbthree1549/ear"/>
+         <fileset dir="${junit.junit.root}">
+            <include name="lib/junit.jar"/>
+         </fileset>         
+      </jar>      
+   </target>
+   
    <target name="jbas4489"
       description="Builds a simple jar files."
       depends="compile-classes">
@@ -3432,7 +3454,7 @@
    <target name="jars" depends="mdbsessionpoolclear, removedislocal, statelesscreation, defaultremotebindings, localfromremote, clusteredjms, entityoptimisticlocking, concurrentnaming, propertyreplacement, persistenceunits, invalidtxmdb, descriptortypo, libdeployment, homeinterface, servicexmbean, arjuna, mdbtransactions, unauthenticatedprincipal, clusteredservice, invoker, classloader, 
       circulardependency, jsp, timerdependency, servicedependency, servlet, stateless14, webservices, ear, ejbthree440, 
       ejbthree454, ejbthree653, ejbthree670, ejbthree712, ejbthree724, ejbthree751, ejbthree832, ejbthree921,
-      ejbthree959, ejbthree963, ejbthree994,
+      ejbthree959, ejbthree963, ejbthree994, ejbthree1549,
       jbas4489,
       aspectdomain, ejbcontext, schema, mail, scopedclassloader, dependency, jaxws,
       pkg, securitydomain, enventry, 
@@ -3619,7 +3641,7 @@
          <sysproperty key="java.naming.provider.url" value="${node0.jndi.url}"/>
 
 
-         <jvmarg line="${jvmargs}" />
+         <jvmarg line="${ejb3.jboss.jvmargs}" />
 
          <classpath>
             <!--path refid="asm.asm.classpath"/-->
@@ -3995,6 +4017,9 @@
          <param name="test" value="ejbthree994"/>
       </antcall>
       <antcall target="test" inheritRefs="true">
+         <param name="test" value="ejbthree1549"/>
+      </antcall>      
+      <antcall target="test" inheritRefs="true">
          <param name="test" value="jbas4489"/>
       </antcall>
    </target>

Modified: branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/main/org/jboss/ejb3/cache/simple/SimpleStatefulCache.java
===================================================================
--- branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/main/org/jboss/ejb3/cache/simple/SimpleStatefulCache.java	2009-02-06 16:29:58 UTC (rev 83967)
+++ branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/main/org/jboss/ejb3/cache/simple/SimpleStatefulCache.java	2009-02-06 16:44:38 UTC (rev 83968)
@@ -23,9 +23,11 @@
 
 import java.util.Iterator;
 import java.util.LinkedHashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.LinkedBlockingQueue;
+
 import javax.ejb.EJBException;
 import javax.ejb.NoSuchEJBException;
 import org.jboss.annotation.ejb.cache.simple.CacheConfig;
@@ -36,13 +38,13 @@
 import org.jboss.ejb3.Pool;
 import org.jboss.ejb3.cache.StatefulCache;
 import org.jboss.ejb3.stateful.StatefulBeanContext;
-import org.jboss.util.id.GUID;
 import org.jboss.logging.Logger;
 
 /**
  * Comment
  *
  * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @author <a href="mailto:galder.zamarreno at jboss.com">Galder Zamarreno</a>
  * @version $Revision$
  */
 public class SimpleStatefulCache implements StatefulCache
@@ -54,15 +56,17 @@
    private int maxSize = 1000;
    private StatefulSessionPersistenceManager pm;
    private long sessionTimeout = 300; // 5 minutes
-   private long removalTimeout = 0; 
+   private long removalTimeout = 0; // 0 == Never
    private SessionTimeoutTask timeoutTask;
    private RemovalTimeoutTask removalTask = null;
    private boolean running = true;
    protected int createCount = 0;
    protected int passivatedCount = 0;
    protected int removeCount = 0;
+   
+   private Queue<StatefulBeanContext> passivationQueue = new LinkedBlockingQueue<StatefulBeanContext>();
 
-   private class CacheMap extends LinkedHashMap
+   protected class CacheMap extends LinkedHashMap
    {
       private static final long serialVersionUID = 4514182777643616159L;
 
@@ -71,6 +75,7 @@
          super(maxSize, 0.75F, true);
       }
 
+      @Override
       public boolean removeEldestEntry(Map.Entry entry)
       {
          boolean removeIt = size() > maxSize;
@@ -102,20 +107,33 @@
       }
    }
    
-   private class RemovalTimeoutTask extends Thread
+   protected class RemovalTimeoutTask extends Thread
    {
       public RemovalTimeoutTask(String name)
       {
          super(name);
       }
 
+      protected void block() throws InterruptedException
+      {
+         Thread.sleep(removalTimeout * 1000);
+      }
+      
+      protected void preRemoval()
+      {
+      }
+      
+      protected void postRemoval()
+      {
+      }
+
       public void run()
       {
          while (running)
          {
             try
             {
-               Thread.sleep(removalTimeout * 1000);
+               block();
             }
             catch (InterruptedException e)
             {
@@ -124,6 +142,9 @@
             }
             try
             {
+               // Invoke pre-removal callback
+               preRemoval();
+               
                long now = System.currentTimeMillis();
                
                synchronized (cacheMap)
@@ -156,6 +177,9 @@
                      remove(centry.getId());
                   }               
                }
+               
+               // Invoke post-removal callback
+               postRemoval();               
             }
             catch (Exception ex)
             {
@@ -165,12 +189,33 @@
       }
    }
 
-   private class SessionTimeoutTask extends Thread
+   protected class SessionTimeoutTask extends Thread
    {
       public SessionTimeoutTask(String name)
       {
          super(name);
       }
+      
+      protected void block() throws InterruptedException
+      {
+         Thread.sleep(sessionTimeout * 1000);
+      }
+      
+      /**
+       * I'm done passivating.
+       */
+      protected void passivationCompleted()
+      {
+         
+      }
+      
+      /**
+       * I'm done selecting candidates for passivation.
+       */
+      protected void prePassivationCompleted()
+      {
+         
+      }      
 
       public void run()
       {
@@ -178,7 +223,7 @@
          {
             try
             {
-               Thread.sleep(sessionTimeout * 1000);
+               block();
             }
             catch (InterruptedException e)
             {
@@ -187,6 +232,13 @@
             }
             try
             {
+               /*
+                * EJBTHREE-1549
+                * 
+                * Passivation is potentially a long-running
+                * operation, so copy the contents quickly and 
+                * perform passivation off a queue.
+                */
                synchronized (cacheMap)
                {
                   if (!running) return;
@@ -206,7 +258,7 @@
                            {
                               if (!centry.getCanRemoveFromCache())
                               {
-                                 passivate(centry);
+                                 passivationQueue.add(centry);
                               }
                               else if (trace)
                               {
@@ -216,6 +268,7 @@
                            else
                            {
                               centry.markedForPassivation = true;                              
+                              assert centry.isInUse() : centry + " is not in use, and thus will never be passivated";
                            }
                            // its ok to evict because it will be passivated
                            // or we determined above that we can remove it
@@ -229,7 +282,19 @@
                      }
                   }
                }
+               
+               prePassivationCompleted();
+               
+               StatefulBeanContext ctx;
+               while ((ctx = passivationQueue.poll()) != null)
+               {  
+                  passivate(ctx);
+               }
+               
+               // Make internal callback that we're done
+               this.passivationCompleted();
             }
+
             catch (Exception ex)
             {
                log.error("problem passivation thread", ex);
@@ -242,7 +307,7 @@
    {
       Advisor advisor = (Advisor) container;
       this.pool = container.getPool();
-      cacheMap = new CacheMap();
+      cacheMap = createCacheMap();
       PersistenceManager pmConfig = (PersistenceManager) advisor.resolveAnnotation(PersistenceManager.class);
       this.pm = (StatefulSessionPersistenceManager) pmConfig.value().newInstance();
       pm.initialize(container);
@@ -298,7 +363,10 @@
       try
       {
          Thread.currentThread().setContextClassLoader(((EJBContainer) ctx.getContainer()).getClassloader());
-         pm.passivateSession(ctx);
+         synchronized(pm)
+         {
+            pm.passivateSession(ctx);
+         }
          ++passivatedCount;
       }
       finally
@@ -321,9 +389,9 @@
          synchronized (cacheMap)
          {
             cacheMap.put(ctx.getId(), ctx);
+            ctx.setInUse(true);
+            ctx.lastUsed = System.currentTimeMillis();
          }
-         ctx.setInUse(true);
-         ctx.lastUsed = System.currentTimeMillis();
          ++createCount;
       }
       catch (EJBException e)
@@ -352,9 +420,9 @@
          synchronized (cacheMap)
          {
             cacheMap.put(ctx.getId(), ctx);
+            ctx.setInUse(true);
+            ctx.lastUsed = System.currentTimeMillis();
          }
-         ctx.setInUse(true);
-         ctx.lastUsed = System.currentTimeMillis();
          ++createCount;
       }
       catch (EJBException e)
@@ -382,26 +450,62 @@
       {
          entry = (StatefulBeanContext) cacheMap.get(key);
       }
-      if (entry == null)
+      if(entry == null)
       {
-         entry = (StatefulBeanContext) pm.activateSession(key);
-         if (entry == null)
+         // TODO: optimize
+         synchronized (cacheMap)
          {
-            throw new NoSuchEJBException("Could not find stateful bean: " + key);
+            entry = (StatefulBeanContext)cacheMap.get(key);
+            if(entry == null)
+            {
+               Iterator<StatefulBeanContext> i = passivationQueue.iterator();
+               while(i.hasNext())
+               {
+                  StatefulBeanContext ctx = i.next();
+                  if(ctx.getId().equals(key))
+                  {
+                     boolean passivationCanceled = passivationQueue.remove(ctx);
+                     if(passivationCanceled)
+                     {
+                        entry = ctx;
+                        cacheMap.put(key, entry);
+                     }
+                     break;
+                  }
+               }
+            }
          }
-         --passivatedCount;
-          
-         // We cache the entry even if we will throw an exception below
-         // as we may still need it for its children and XPC references
-         if (log.isTraceEnabled())
+      }
+      if (entry == null)
+      {
+         synchronized(pm)
          {
-            log.trace("Caching activated context " + entry.getId() + " of type " + entry.getClass());
+            synchronized (cacheMap)
+            {
+               entry = (StatefulBeanContext)cacheMap.get(key);
+            }
+            if(entry == null)
+            {
+               entry = pm.activateSession(key);
+               if (entry == null)
+               {
+                  throw new NoSuchEJBException("Could not find stateful bean: " + key);
+               }
+               --passivatedCount;
+               
+               // We cache the entry even if we will throw an exception below
+               // as we may still need it for its children and XPC references
+               if (log.isTraceEnabled())
+               {
+                  log.trace("Caching activated context " + entry.getId() + " of type " + entry.getClass());
+               }
+               
+               synchronized (cacheMap)
+               {
+                  cacheMap.put(key, entry);
+               }
+            }
          }
-
-         synchronized (cacheMap)
-         {
-            cacheMap.put(key, entry);
-         }
       }
       
       // Now we know entry isn't null
@@ -435,24 +539,27 @@
 
    public void remove(Object key)
    {
+      if(log.isTraceEnabled())
+      {
+         log.trace("Removing context " + key);
+      }
       StatefulBeanContext ctx = null;
       synchronized (cacheMap)
       {
          ctx = (StatefulBeanContext) cacheMap.get(key);
       }
-      if (ctx != null) 
+      if(ctx == null)
+         throw new NoSuchEJBException("Could not find Stateful bean: " + key);
+      if (!ctx.isRemoved())
+         pool.remove(ctx);
+      
+      ++removeCount;
+
+      if (ctx.getCanRemoveFromCache())
       {
-         if (!ctx.isRemoved())
-            pool.remove(ctx);
-         
-         ++removeCount;
-         
-         if (ctx.getCanRemoveFromCache())
+         synchronized (cacheMap)
          {
-            synchronized (cacheMap)
-            {
-               cacheMap.remove(key);
-            }
+            cacheMap.remove(key);
          }
       }
    }
@@ -464,7 +571,7 @@
    
    public int getTotalSize()
    {
-      return cacheMap.size() + pm.getNumPassivatedBeans();
+      return cacheMap.size() + passivatedCount;
    }
    
    public int getCreateCount()
@@ -496,4 +603,44 @@
    {
       return cacheMap.size();
    }
+   
+   protected CacheMap createCacheMap()
+   {
+      return new CacheMap(); 
+   }
+   
+   protected StatefulSessionPersistenceManager getPersistenceManager()
+   {
+      return pm;
+   }
+   
+   protected SessionTimeoutTask getTimeoutTask()
+   {
+      return timeoutTask;
+   }
+   
+   protected void setTimeoutTask(SessionTimeoutTask timeoutTask)
+   {
+      this.timeoutTask = timeoutTask;
+   }   
+   
+   protected long getSessionTimeout()
+   {
+      return sessionTimeout;
+   }
+
+   protected long getRemovalTimeout()
+   {
+      return removalTimeout;
+   }
+
+   protected RemovalTimeoutTask getRemovalTask()
+   {
+      return removalTask;
+   }
+
+   protected void setRemovalTask(RemovalTimeoutTask removalTask)
+   {
+      this.removalTask = removalTask;
+   }
 }

Modified: branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/main/org/jboss/ejb3/stateful/StatefulLocalProxy.java
===================================================================
--- branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/main/org/jboss/ejb3/stateful/StatefulLocalProxy.java	2009-02-06 16:29:58 UTC (rev 83967)
+++ branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/main/org/jboss/ejb3/stateful/StatefulLocalProxy.java	2009-02-06 16:44:38 UTC (rev 83968)
@@ -142,4 +142,8 @@
       return proxyName;
    }
 
+   public Object getId()
+   {
+      return id;
+   }
 }

Added: branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/resources/test/ejbthree1549/ear/META-INF/application.xml
===================================================================
--- branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/resources/test/ejbthree1549/ear/META-INF/application.xml	                        (rev 0)
+++ branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/resources/test/ejbthree1549/ear/META-INF/application.xml	2009-02-06 16:44:38 UTC (rev 83968)
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE application PUBLIC
+   "-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN"
+   "http://java.sun.com/dtd/application_1_3.dtd">
+
+<application>
+   <display-name>ejbthree1549</display-name>
+
+   <module>
+      <ejb>ejbthree1549.jar</ejb>
+   </module>
+</application>
\ No newline at end of file

Added: branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/BlockingPersistenceManager.java
===================================================================
--- branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/BlockingPersistenceManager.java	                        (rev 0)
+++ branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/BlockingPersistenceManager.java	2009-02-06 16:44:38 UTC (rev 83968)
@@ -0,0 +1,178 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+  *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.ejb3.test.ejbthree1549;
+
+import java.io.IOException;
+import java.rmi.MarshalledObject;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.ejb.EJBException;
+
+import org.jboss.ejb3.Container;
+import org.jboss.ejb3.cache.simple.StatefulSessionPersistenceManager;
+import org.jboss.ejb3.stateful.StatefulBeanContext;
+import org.jboss.logging.Logger;
+
+/**
+ * BlockingPersistenceManager
+ * 
+ * An implementation of a PersistenceManager which, instead of
+ * persisting directly, exposes a blocking mechanism allowing 
+ * tests to control when passivation occurs 
+ *
+ * @author <a href="mailto:andrew.rubinger at jboss.org">ALR</a>
+ * @author <a href="mailto:galder.zamarreno at jboss.com">Galder Zamarreno</a>
+ * @version $Revision: $
+ */
+public class BlockingPersistenceManager implements StatefulSessionPersistenceManager
+{
+   // --------------------------------------------------------------------------------||
+   // Class Members ------------------------------------------------------------------||
+   // --------------------------------------------------------------------------------||
+
+   private static final Logger log = Logger.getLogger(BlockingPersistenceManager.class);
+
+   /**
+    * Publicly-accessible lock
+    * 
+    * Used by the test to block the act of passivation
+    */
+   public static final Lock PASSIVATION_LOCK = new ReentrantLock();
+
+   /**
+    * Publicly-accessible barrier
+    * 
+    * Will block until both the test and the PM agree that passivation 
+    * should take place
+    */
+   public static final CyclicBarrier BARRIER = new CyclicBarrier(2);
+   
+   private Map<Object, MarshalledObject> passivated = new ConcurrentHashMap<Object, MarshalledObject>();
+
+   // --------------------------------------------------------------------------------||
+   // Required Implementations -------------------------------------------------------||
+   // --------------------------------------------------------------------------------||
+
+   public StatefulBeanContext activateSession(Object id)
+   {
+      log.info("Activating " + id);
+      MarshalledObject o = passivated.remove(id);
+      if(o == null)
+         throw new EJBException("Can't find bean " + id);
+      try
+      {
+         return (StatefulBeanContext)o.get();
+      }
+      catch (IOException e)
+      {
+         throw new EJBException(e);
+      }
+      catch (ClassNotFoundException e)
+      {
+         throw new EJBException(e);
+      }
+   }
+
+   public void destroy() throws Exception
+   {
+      passivated.clear();
+   }
+
+   public List<StatefulBeanContext> getPassivatedBeans()
+   {
+      // very stupid and slow, don't do this
+      throw new RuntimeException("NYI");
+   }
+
+   public void initialize(Container container) throws Exception
+   {
+   }
+   
+   public void passivateSession(StatefulBeanContext ctx)
+   {
+
+      try
+      {
+         /*
+          * Block until the lock may be acquired,
+          * may currently be held by the test Thread until the test is ready.
+          * So here both the test and passivation will block until this barrier
+          * is agreed by both Threads to be released
+          */
+         log.info("Waiting until the test is ready for passivation to start...");
+         BARRIER.await();
+
+         // Block until the test releases this lock
+         log.info("Blocking until the test tells us that the act of passivation may continue...");
+         PASSIVATION_LOCK.lock();
+
+         try
+         {
+            // Mock Passivate
+            log.info("Mock Passivation on " + ctx.getId());
+            passivated.put(ctx.getId(), new MarshalledObject(ctx));
+         }
+         catch(IOException e)
+         {
+            throw new EJBException(e);
+         }
+         finally
+         {
+            // Release the passivation lock
+            log.info("We're done with passivation, letting the lock go.");
+            PASSIVATION_LOCK.unlock();
+         }
+      }
+      catch (InterruptedException e)
+      {
+         throw new RuntimeException("Barrier was interrupted prematurely", e);
+      }
+      catch (BrokenBarrierException e)
+      {
+         throw new RuntimeException("Barrier was broken prematurely", e);
+      }
+      finally
+      {
+         // Reset the Barrier
+         BARRIER.reset();
+      }
+   }
+   
+   public void removePassivated(Object id)
+   {
+      MarshalledObject o = passivated.remove(id);
+      if(o == null)
+         throw new EJBException("Can't find bean " + id);
+   }
+
+   public int getNumPassivatedBeans()
+   {
+      return passivated.size();
+   }
+
+}

Added: branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/ContainerAccessInterceptor.java
===================================================================
--- branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/ContainerAccessInterceptor.java	                        (rev 0)
+++ branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/ContainerAccessInterceptor.java	2009-02-06 16:44:38 UTC (rev 83968)
@@ -0,0 +1,62 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.ejb3.test.ejbthree1549;
+
+import org.jboss.aop.advice.Interceptor;
+import org.jboss.aop.joinpoint.Invocation;
+import org.jboss.ejb3.stateful.StatefulContainer;
+import org.jboss.ejb3.stateful.StatefulContainerInvocation;
+import org.jboss.logging.Logger;
+
+/**
+ * ContainerAccessInterceptor.
+ * 
+ * @author <a href="mailto:galder.zamarreno at jboss.com">Galder Zamarreno</a>
+ */
+public class ContainerAccessInterceptor implements Interceptor
+{
+   private static final Logger log = Logger.getLogger(ContainerAccessInterceptor.class);
+   
+   public String getName()
+   {
+      return ContainerAccessInterceptor.class.getSimpleName();
+   }
+
+   public Object invoke(Invocation arg0) throws Throwable
+   {
+      try
+      {
+         return arg0.invokeNext();
+      }
+      finally
+      {
+         if (MyStatefulCacheProxyBean.myStatefulBeancache == null)
+         {
+            StatefulContainerInvocation ejb = (StatefulContainerInvocation) arg0;
+            StatefulContainer container = (StatefulContainer) ejb.getBeanContext().getContainer();
+            MyStatefulCacheProxyBean.myStatefulBeancache = (ForceEventsCache)container.getCache();
+            log.info("Set cache proxy bean's cache instance " + MyStatefulCacheProxyBean.myStatefulBeancache);
+         }
+         
+      }
+   }
+}

Added: branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/ForceEventsCache.java
===================================================================
--- branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/ForceEventsCache.java	                        (rev 0)
+++ branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/ForceEventsCache.java	2009-02-06 16:44:38 UTC (rev 83968)
@@ -0,0 +1,385 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+  *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.ejb3.test.ejbthree1549;
+
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CyclicBarrier;
+
+import org.jboss.ejb3.cache.simple.SimpleStatefulCache;
+import org.jboss.ejb3.stateful.StatefulBeanContext;
+import org.jboss.logging.Logger;
+
+/**
+ * ForceEventsCache
+ * 
+ * An extension of the SimpleStatefulCache which provides for
+ * forcing the Passivation and Removal tasks to run, also supplanting
+ * barriers and locks throughout the tasks' lifecycle callbacks
+ *
+ * @author <a href="mailto:andrew.rubinger at jboss.org">ALR</a>
+ * @version $Revision: $
+ */
+public class ForceEventsCache extends SimpleStatefulCache
+{
+   // --------------------------------------------------------------------------------||
+   // Class Members ------------------------------------------------------------------||
+   // --------------------------------------------------------------------------------||
+
+   private static final Logger log = Logger.getLogger(ForceEventsCache.class);
+
+   /**
+    * Shared barrier between the Cache and the test so that 
+    * the test may block until passivation is completed
+    */
+   public static final CyclicBarrier POST_PASSIVATE_BARRIER = new CyclicBarrier(2);
+
+   /**
+    * Internal lock used to manually block the passivation task from running
+    */
+   private static final Object START_PASSIVATION_LOCK = new Object();
+
+   private static volatile boolean passivationForced = false;
+
+   /**
+    * Internal lock used to manually block the removal task from running
+    */
+   private static final Object START_REMOVAL_LOCK = new Object();
+
+   /**
+    * Flag that removal has been forced
+    */
+   private static volatile boolean removalForced = false;
+
+   public static final CyclicBarrier PRE_PASSIVATE_BARRIER = new CyclicBarrier(2);
+
+   /**
+    * Public barrier for removal to block until both test and removal tasks are ready
+    */
+   public static final CyclicBarrier PRE_REMOVE_BARRIER = new CyclicBarrier(2);
+   
+   private CacheMap cacheMap;
+
+   // --------------------------------------------------------------------------------||
+   // Functional Methods -------------------------------------------------------------||
+   // --------------------------------------------------------------------------------||
+
+   public void clear()
+   {
+      cacheMap.clear();
+   }
+
+   /**
+    * Triggers passivation to run
+    */
+   public static void forcePassivation()
+   {
+      // Get a lock
+      log.info("Awaiting lock to force passivation");
+      synchronized (START_PASSIVATION_LOCK)
+      {
+         passivationForced = true;
+         // Notify that passivation should run
+         log.info("Notifying passivation via manual force...");
+         START_PASSIVATION_LOCK.notify();
+      }
+   }
+
+   /**
+    * Triggers removal to run
+    */
+   public static void forceRemoval()
+   {
+      // Get a lock
+      log.info("Awaiting lock to force removal");
+      synchronized (START_REMOVAL_LOCK)
+      {
+         removalForced = true;
+         // Notify that removal should run
+         log.info("Notifying removal via manual force...");
+         START_REMOVAL_LOCK.notify();
+      }
+   }
+
+   /**
+    * Manually sets the session with the specified sessionId
+    * past expiry for passivation
+    * 
+    * @param sessionId
+    */
+   public void makeSessionEligibleForPassivation(Object sessionId)
+   {
+      this.setSessionLastUsedPastTimeout(sessionId, this.getSessionTimeout());
+   }
+
+   /**
+    * Manually sets the session with the specified sessionId
+    * past expiry for removal
+    * 
+    * @param sessionId
+    */
+   public void makeSessionEligibleForRemoval(Object sessionId)
+   {
+      this.setSessionLastUsedPastTimeout(sessionId, this.getRemovalTimeout());
+   }
+
+   /**
+    * Exposed for testing only
+    * 
+    * Returns whether or not the internal cacheMap contains
+    * the specified key
+    * 
+    * @return
+    */
+   public boolean doesCacheMapContainKey(Object sessionId)
+   {
+      // Get the cacheMap
+      CacheMap cm = this.cacheMap;
+
+      // Synchronize on it
+      synchronized (cm)
+      {
+         // Return whether the specified key was found
+         return cm.containsKey(sessionId);
+      }
+   }
+
+   // --------------------------------------------------------------------------------||
+   // Overridden Implementations -----------------------------------------------------||
+   // --------------------------------------------------------------------------------||
+
+   /**
+    * Lifecycle start, overridden to switch up the Passivate and Removal tasks to
+    * test-specific implementations
+    */
+   @Override
+   public void start()
+   {
+      // Initialize
+      String threadNamePrefix = "EJBTHREE-1549 SFSB Thread: ";
+
+      // Switch up the Passivation and Removal Tasks to blocking implementations
+      this.setTimeoutTask(new BlockingPassivationTask(threadNamePrefix + "PASSIVATION"));
+      this.setRemovalTask(new BlockingRemovalTask(threadNamePrefix + "REMOVAL"));
+
+      // Call super implementation
+      super.start();
+
+   }
+   
+   @Override
+   protected CacheMap createCacheMap()
+   {
+      cacheMap = super.createCacheMap(); 
+      return cacheMap; 
+   }
+
+
+
+   /**
+    * BlockingRemovalTask
+    * 
+    * An extension of the default removal task which, instead 
+    * of waiting for a timeout, will block until forced
+    *
+    * @author <a href="mailto:andrew.rubinger at jboss.org">ALR</a>
+    * @version $Revision: $
+    */
+   private class BlockingRemovalTask extends RemovalTimeoutTask
+   {
+      public BlockingRemovalTask(String name)
+      {
+         super(name);
+      }
+
+      @Override
+      public void block() throws InterruptedException
+      {
+         // Get a lock on our monitor
+         synchronized (START_REMOVAL_LOCK)
+         {
+            if (!removalForced)
+            {
+               // Wait until we're signaled
+               log.info("Waiting to be notified to run removal...");
+               START_REMOVAL_LOCK.wait();
+            }
+            removalForced = false;
+         }
+
+         // Log that we've been notified
+         log.info("Notified to run removal");
+      }
+
+      @Override
+      protected void preRemoval()
+      {
+
+         // Block until the barrier is cleared
+         try
+         {
+            PRE_REMOVE_BARRIER.await();
+         }
+         catch (InterruptedException e)
+         {
+            throw new RuntimeException(e);
+         }
+         catch (BrokenBarrierException e)
+         {
+            throw new RuntimeException(e);
+         }
+
+         // Invoke super implementation
+         super.preRemoval();
+      }
+
+   }
+
+   /**
+    * BlockingPassivationTask
+    * 
+    * An extension of the default timeout task which, instead of 
+    * waiting for a timeout, will await (block until) notification that passivation
+    * should run
+    *
+    * @author <a href="mailto:andrew.rubinger at jboss.org">ALR</a>
+    * @version $Revision: $
+    */
+   private class BlockingPassivationTask extends SessionTimeoutTask
+   {
+
+      public BlockingPassivationTask(String name)
+      {
+         super(name);
+      }
+
+      @Override
+      public void block() throws InterruptedException
+      {
+         // Get a lock on our monitor
+         synchronized (START_PASSIVATION_LOCK)
+         {
+            if (!passivationForced)
+            {
+               // Wait until we're signaled
+               log.info("Waiting to be notified to run passivation...");
+               START_PASSIVATION_LOCK.wait();
+            }
+            passivationForced = false;
+         }
+
+         // Log that we've been notified
+         log.info("Notified to run passivation");
+      }
+
+      @Override
+      protected void passivationCompleted()
+      {
+         // Call super
+         super.passivationCompleted();
+
+         // Tell the barrier we've arrived
+         try
+         {
+            log.info("Waiting on the post-passivate barrier...");
+            POST_PASSIVATE_BARRIER.await();
+         }
+         catch (InterruptedException e)
+         {
+            throw new RuntimeException("Post Passivate prematurely interrupted", e);
+         }
+         catch (BrokenBarrierException e)
+         {
+            throw new RuntimeException("Post Passivate prematurely broken", e);
+         }
+         finally
+         {
+            // Reset the barrier
+            log.info("Post-passivate of PM is done, resetting the barrier");
+            POST_PASSIVATE_BARRIER.reset();
+         }
+      }
+
+      @Override
+      protected void prePassivationCompleted()
+      {
+         super.prePassivationCompleted();
+
+         try
+         {
+            PRE_PASSIVATE_BARRIER.await();
+         }
+         catch (BrokenBarrierException e)
+         {
+            throw new RuntimeException("PRE_PASSIVATE_BARRIER prematurely broken", e);
+         }
+         catch (InterruptedException e)
+         {
+            throw new RuntimeException(e);
+         }
+      }
+   }
+
+   // --------------------------------------------------------------------------------||
+   // Internal Helper Methods --------------------------------------------------------||
+   // --------------------------------------------------------------------------------||
+
+   /**
+    * Obtains a time in the past further away than the specified timeout value,
+    * expressed in milliseconds since the epoch (per contract of System.currentTimeMillis()
+    * 
+    * @param timeoutValue
+    * @return
+    */
+   private long getExpiredTime(long timeoutValue)
+   {
+      long now = System.currentTimeMillis();
+      return (now - (timeoutValue * 1000)) - 1;
+   }
+
+   /**
+    * Marks the session with the specified ID as last used past the 
+    * specified timeout period
+    * 
+    * @param sessionId
+    * @param timeout
+    */
+   private void setSessionLastUsedPastTimeout(Object sessionId, long timeout)
+   {
+      // Get the cacheMap
+      CacheMap cm = this.cacheMap;
+
+      // Synchronize on it
+      synchronized (cm)
+      {
+         // Find the session
+         StatefulBeanContext session = (StatefulBeanContext) cm.get(sessionId);
+
+         // Synchronize on the session
+         synchronized (session)
+         {
+            // Manually set past expiry
+            session.lastUsed = this.getExpiredTime(timeout);
+         }
+      }
+   }
+
+}
\ No newline at end of file

Added: branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/MyStatefulBean.java
===================================================================
--- branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/MyStatefulBean.java	                        (rev 0)
+++ branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/MyStatefulBean.java	2009-02-06 16:44:38 UTC (rev 83968)
@@ -0,0 +1,88 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+  *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.ejb3.test.ejbthree1549;
+
+import javax.ejb.Local;
+import javax.ejb.Stateful;
+
+import org.jboss.annotation.ejb.AspectDomain;
+import org.jboss.annotation.ejb.LocalBinding;
+import org.jboss.annotation.ejb.cache.Cache;
+import org.jboss.annotation.ejb.cache.simple.CacheConfig;
+import org.jboss.annotation.ejb.cache.simple.PersistenceManager;
+
+/**
+ * MyStatefulBean
+ * 
+ * A SFSB with the following overrides:
+ * 
+ * 1) Uses a Cache implementation that provides mechanism to
+ * block tasks such as passivation and removal, in addition to 
+ * adding support for internal lifecycle implementations for these
+ * tasks
+ * 
+ * 2) Uses a backing PersistenceManager that provides mechanism to
+ * block completion of passivation from the test until ready
+ *
+ * @author <a href="mailto:andrew.rubinger at jboss.org">ALR</a>
+ * @author <a href="mailto:galder.zamarreno at jboss.com">Galder Zamarreno</a>
+ * @version $Revision: $
+ */
+ at Stateful
+ at Local(MyStatefulLocal.class)
+ at LocalBinding(jndiBinding = MyStatefulLocal.JNDI_NAME)
+/*
+ * Use a CacheFactory that is extended to enable 
+ * blocking hooks that we need for testing
+ */
+ at Cache(ForceEventsCache.class)
+/*
+ * Make instances eligible for timeout very soon after last invocation, and be removed (as default is NEVER)
+ */
+ at CacheConfig(idleTimeoutSeconds = MyStatefulLocal.PASSIVATION_TIMEOUT, removalTimeoutSeconds = MyStatefulLocal.REMOVAL_TIMEOUT)
+/*
+ * Set up a persistence manager that allows us to block, and therefore
+ * lets the test decide how long the processes of performing passivation
+ * should take.  Used to manually interleave Threads to target the test case.
+ */
+ at PersistenceManager(BlockingPersistenceManager.class)
+ at AspectDomain("My Stateful Bean")
+public class MyStatefulBean implements MyStatefulLocal 
+{
+   // --------------------------------------------------------------------------------||
+   // Instance Members ---------------------------------------------------------------||
+   // --------------------------------------------------------------------------------||
+
+   private int counter;
+
+   // --------------------------------------------------------------------------------||
+   // Required Implementations -------------------------------------------------------||
+   // --------------------------------------------------------------------------------||
+
+   /**
+    * Returns and increments the counter, which starts at 0
+    */
+   public int getNextCounter()
+   {
+      return counter++;
+   }   
+}

Added: branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/MyStatefulCacheProxyBean.java
===================================================================
--- branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/MyStatefulCacheProxyBean.java	                        (rev 0)
+++ branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/MyStatefulCacheProxyBean.java	2009-02-06 16:44:38 UTC (rev 83968)
@@ -0,0 +1,668 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.ejb3.test.ejbthree1549;
+
+import java.lang.reflect.Proxy;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.ejb.Remote;
+import javax.ejb.Stateful;
+import javax.naming.InitialContext;
+
+import junit.framework.TestCase;
+
+import org.jboss.annotation.ejb.RemoteBinding;
+import org.jboss.ejb3.stateful.StatefulLocalProxy;
+import org.jboss.logging.Logger;
+
+/**
+ * MyStatefulCacheProxyBean.
+ * 
+ * @author <a href="mailto:galder.zamarreno at jboss.com">Galder Zamarreno</a>
+ */
+ at Stateful
+ at Remote(MyStatefulCacheProxyRemote.class)
+ at RemoteBinding(jndiBinding = MyStatefulCacheProxyRemote.JNDI_NAME)
+public class MyStatefulCacheProxyBean implements MyStatefulCacheProxyRemote
+{
+   private static final Logger log = Logger.getLogger(MyStatefulCacheProxyBean.class);
+   
+   public static ForceEventsCache myStatefulBeancache;
+   
+   public void makeSessionEligibleForRemoval(Object sessionId)
+   {
+      myStatefulBeancache.makeSessionEligibleForRemoval(sessionId);
+   }
+   
+   public void forceRemoval()
+   {
+      ForceEventsCache.forceRemoval();
+   }
+   
+   public void preRemoveBarrierAwait() throws InterruptedException, BrokenBarrierException
+   {
+      ForceEventsCache.PRE_REMOVE_BARRIER.await();
+   }
+
+   public void forcePassivation()
+   {
+      ForceEventsCache.forcePassivation();
+   }
+   
+   public void prePassivateBarrierAwait() throws InterruptedException, BrokenBarrierException
+   {
+      ForceEventsCache.PRE_PASSIVATE_BARRIER.await();
+   }
+   
+   public void prePassivateBarrierAwait(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException
+   {
+      ForceEventsCache.PRE_PASSIVATE_BARRIER.await(timeout, unit);
+   }
+
+   public void postPassivateBarrierAwait(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException
+   {
+      ForceEventsCache.POST_PASSIVATE_BARRIER.await(timeout, unit);
+   }
+
+   public void makeSessionEligibleForPassivation(Object sessionId)
+   {
+      myStatefulBeancache.makeSessionEligibleForPassivation(sessionId);
+   }
+
+   public void pmBarrierAwait(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException,
+         TimeoutException
+   {
+      BlockingPersistenceManager.BARRIER.await(timeout, unit);
+   }
+   
+   public boolean pmPassivationLockTryLock()
+   {
+      log.info("Persistence Manager tryLock");
+      return BlockingPersistenceManager.PASSIVATION_LOCK.tryLock();
+   }
+   
+   public void pmPassivationLockUnlock()
+   {
+      log.info("Persistence Manager unlock");
+      BlockingPersistenceManager.PASSIVATION_LOCK.unlock();
+   }
+   
+   public boolean doesCacheMapContainKey(Object sessionId)
+   {
+      return myStatefulBeancache.doesCacheMapContainKey(sessionId);
+   }
+   
+   public void resetAndClear()
+   {
+      ForceEventsCache.POST_PASSIVATE_BARRIER.reset();
+      ForceEventsCache.PRE_PASSIVATE_BARRIER.reset();
+      ForceEventsCache.PRE_REMOVE_BARRIER.reset();
+      BlockingPersistenceManager.BARRIER.reset();
+      myStatefulBeancache.clear();  
+   }
+   
+   public void testSessionRemovalDuringPassivation() throws Throwable
+   {
+      try
+      {
+         /*
+          * First we invoke upon a new session
+          */
+
+         // Create a new session, which should be allowed during passivation
+         final MyStatefulLocal bean = (MyStatefulLocal) new InitialContext().lookup(MyStatefulLocal.JNDI_NAME);
+
+         // If we've got nothing
+         if (bean == null)
+         {
+            // Fail
+            TestCase.fail("Lookup did not succeed");
+         }
+
+         // Invoke upon the new Session
+         final int next = bean.getNextCounter();
+
+         // Test the value is expected
+         TestCase.assertEquals("Next counter received was not expected", 0, next);
+
+         /*
+          * Mark this session as eligible for removal
+          */
+
+         // Get the Session ID
+         Object sessionId = getSessionId(bean);
+
+         // Mark
+         makeSessionEligibleForRemoval(sessionId);
+
+         /*
+          * All's OK with traditional invocation, so define a task to force removal 
+          * of the SFSB, but don't invoke it yet (we're going to fire this off 
+          * *during* passivation
+          */
+
+         // Define the task to invoke upon a SFSB, and trigger removal while passivation is suspended
+         Callable<Boolean> invokeDuringPasssivationTest = new Callable<Boolean>()
+         {
+            /**
+             * Force removal
+             */
+            public Boolean call()
+            {
+
+               /*
+                * Force removal
+                */
+
+               // Force
+               forceRemoval();
+
+               // Clear the barrier
+               try
+               {
+                  log.info("Test is waiting on the pre-remove barrier");
+                  preRemoveBarrierAwait();
+                  log.info("Test has cleared the pre-remove barrier");
+               }
+               catch (InterruptedException e)
+               {
+                  throw new RuntimeException(e);
+               }
+               catch (BrokenBarrierException e)
+               {
+                  throw new RuntimeException(e);
+               }
+
+               // Return OK
+               return true;
+
+            }
+         };
+
+         /*
+          * Force passivation, but block it from completing (because we don't 
+          * await on the PM)
+          */
+         
+         // Force passivation
+         forcePassivation();
+         prePassivateBarrierAwait();
+
+         /*
+          * Spawn off the test in another Thread
+          */
+
+         ExecutorService executor = Executors.newFixedThreadPool(1);
+         Future<Boolean> futureResult = executor.submit(invokeDuringPasssivationTest);
+         Boolean result = null;
+
+         /*
+          * Try to get the result of the test to remove, which 
+          * should not be blocked by passivation already in progress
+          */
+
+         // Get result
+         log.info("Attempting to get the result of the removal task...");
+         result = futureResult.get(5, TimeUnit.SECONDS);
+
+         // Make sure the result is expected
+         TestCase.assertTrue("Removal task completed when expected, but got wrong result", result);
+      }
+      catch(Throwable t)
+      {
+         log.error("Throwable thrown", t);
+         throw t;
+      }
+      finally
+      {
+         log.info("Resetting all barriers and clearing the cache...");
+         resetAndClear();         
+      }
+   }
+   
+   public void testInvokeSameSessionDuringPassivation() throws Throwable
+   {
+      try
+      {
+         final MyStatefulLocal bean = (MyStatefulLocal) new InitialContext().lookup(MyStatefulLocal.JNDI_NAME);
+         
+         // Get our bean's Session ID
+         Object sessionId = getSessionId(bean);
+         
+         // Invoke upon our bean
+         int next = bean.getNextCounter();
+         log.info("Got counter from " + sessionId + ": " + next);
+         TestCase.assertEquals("SFSB did not return expected next counter", 0, next);
+
+         // Get the lock to block the PM, now
+         boolean gotLock = pmPassivationLockTryLock();
+
+         Future<Integer> result;
+         // Once PM lock is acquired, everything is in "try" so we release in "finally"
+         try
+         {
+            // Ensure we got the PM lock, else fail the test
+            TestCase.assertTrue("Test was not able to immediately get the lock to block the PersistenceManager", gotLock);
+            log.info("Locked " + BlockingPersistenceManager.class.getSimpleName());
+
+            // Mark
+            makeSessionEligibleForPassivation(sessionId);
+
+            /*
+             * Passivate
+             */
+
+            // Trigger Passivation
+            forcePassivation();
+            log.info("Passivation forced, carrying out test");
+
+            prePassivateBarrierAwait(5, TimeUnit.SECONDS);
+
+            // Block until the PM is ready to passivate
+            log.info("Waiting on common barrier for PM to run...");
+            pmBarrierAwait(5, TimeUnit.SECONDS);
+            log.info("PM and test have met barrier, passivation running (but will be blocked to complete by test)");
+
+            Callable<Integer> task = new Callable<Integer>()
+            {
+               public Integer call() throws Exception
+               {
+                  return bean.getNextCounter();
+               }
+            };
+            ExecutorService executor = Executors.newFixedThreadPool(1);
+            result = executor.submit(task);
+
+            // TODO: there is no way to know where we are in StatefulInstanceInterceptor
+            Thread.sleep(5000);
+         }
+         finally
+         {
+            // Allow the Persistence Manager to finish up
+            log.info("Letting the PM perform passivation...");
+            pmPassivationLockUnlock();
+         }
+
+         // We need to allow time to let the Cache finish passivation, so block until it's done
+         log.info("Waiting on Cache to tell us passivation is completed...");
+         postPassivateBarrierAwait(5, TimeUnit.SECONDS);
+         log.info("Test sees Cache reports passivation completed.");
+
+         int duringPassivation = result.get(5, TimeUnit.SECONDS);
+         log.info("Got counter from " + sessionId + ": " + duringPassivation);
+
+         int postPassivation = bean.getNextCounter();
+         log.info("Got counter from " + sessionId + ": " + postPassivation);
+
+         TestCase.assertEquals("the postPassivation counter should be 1 higher than the previous (during passivation)",
+               duringPassivation + 1, postPassivation);         
+      }
+      catch(Throwable t)
+      {
+         log.error("Throwable thrown", t);
+         throw t;
+      }
+      finally
+      {
+         log.info("Resetting all barriers and clearing the cache...");
+         resetAndClear();
+      }
+   }
+
+   public void testInvokeSameSessionDuringPrePassivation() throws Throwable
+   {
+      try
+      {
+         final MyStatefulLocal bean = (MyStatefulLocal) new InitialContext().lookup(MyStatefulLocal.JNDI_NAME);
+
+         // Get our bean's Session ID
+         Object sessionId = getSessionId(bean);
+
+         // Invoke upon our bean
+         int next = bean.getNextCounter();
+         log.info("Got counter from " + sessionId + ": " + next);
+         TestCase.assertEquals("SFSB did not return expected next counter", 0, next);
+
+         // Get the lock to block the PM, now
+         boolean gotLock = pmPassivationLockTryLock();
+
+         Future<Integer> result;
+         // Once PM lock is acquired, everything is in "try" so we release in "finally"
+         try
+         {
+            // Ensure we got the PM lock, else fail the test
+            TestCase.assertTrue("Test was not able to immediately get the lock to block the PersistenceManager", gotLock);
+            log.info("Locked " + BlockingPersistenceManager.class.getSimpleName());
+
+            // Mark
+            makeSessionEligibleForPassivation(sessionId);
+
+            /*
+             * Passivate
+             */
+
+            // Trigger Passivation
+            forcePassivation();
+            log.info("Passivation forced, carrying out test");
+
+            Callable<Integer> task = new Callable<Integer>()
+            {
+               public Integer call() throws Exception
+               {
+                  return bean.getNextCounter();
+               }
+            };
+            ExecutorService executor = Executors.newFixedThreadPool(1);
+            result = executor.submit(task);
+
+            // TODO: there is no way to know where we are in StatefulInstanceInterceptor
+            Thread.sleep(5000);
+
+            prePassivateBarrierAwait(5, TimeUnit.SECONDS);
+
+            // Block until the PM is ready to passivate
+            /* we're not passivating, we yanked it out
+            log.info("Waiting on common barrier for PM to run...");
+            BlockingPersistenceManager.BARRIER.await(5, TimeUnit.SECONDS);
+            log.info("PM and test have met barrier, passivation running (but will be blocked to complete by test)");
+            */
+         }
+         finally
+         {
+            // Allow the Persistence Manager to finish up
+            log.info("Letting the PM perform passivation...");
+            pmPassivationLockUnlock();
+         }
+
+         // We need to allow time to let the Cache finish passivation, so block until it's done
+         log.info("Waiting on Cache to tell us passivation is completed...");
+         postPassivateBarrierAwait(5, TimeUnit.SECONDS);
+         log.info("Test sees Cache reports passivation completed.");
+
+         int duringPassivation = result.get(5, TimeUnit.SECONDS);
+         log.info("Got counter from " + sessionId + ": " + duringPassivation);
+
+         int postPassivation = bean.getNextCounter();
+         log.info("Got counter from " + sessionId + ": " + postPassivation);
+
+         TestCase.assertEquals("the postPassivation counter should be 1 higher than the previous (during passivation)",
+               duringPassivation + 1, postPassivation);         
+      }
+      catch(Throwable t)
+      {
+         log.error("Throwable thrown", t);
+         throw t;
+      }
+      finally
+      {
+         log.info("Resetting all barriers and clearing the cache...");
+         resetAndClear();         
+      }      
+   }
+
+   public void testNewSessionMayBeCreatedDuringPassivation() throws Throwable
+   {
+      try
+      {
+         // Initialize
+         final String sfsbJndiName = MyStatefulLocal.JNDI_NAME;
+
+         // Define the task to lookup a new session
+         Callable<Boolean> lookupNewSessionTest = new Callable<Boolean>()
+         {
+            /**
+             * Lookup a new session, and set that the test succeeded if we're able
+             */
+            public Boolean call()
+            {
+               try
+               {
+                  // Create a new session, which should be allowed during passivation
+                  MyStatefulLocal bean2 = (MyStatefulLocal) new InitialContext().lookup(sfsbJndiName);
+
+                  // If we've don't have a new session
+                  if (bean2 == null)
+                  {
+                     throw new RuntimeException("Got back null SFSB");
+                  }
+
+               }
+               // We can't fail the unit test from here, so log the error
+               catch (Exception e)
+               {
+                  log.error("Test encountered an error", e);
+               }
+
+               // Test is good
+               return true;
+            }
+         };
+
+         // Run the Test
+         runTestDuringPassivation(lookupNewSessionTest);         
+      }
+      catch(Throwable t)
+      {
+         log.error("Throwable thrown", t);
+         throw t;
+      }
+      finally
+      {
+         log.info("Resetting all barriers and clearing the cache...");
+         resetAndClear();
+      }
+   }
+   
+   public void testSessionMayBeInvokedWhileAnotherIsPassivating() throws Throwable
+   {
+      try
+      {
+         // Initialize
+         final String sfsbJndiName = MyStatefulLocal.JNDI_NAME;
+
+         // Define the task to invoke upon a SFSB
+         Callable<Boolean> invokeDuringPasssivationTest = new Callable<Boolean>()
+         {
+            /**
+             * Lookup a new session, invoke upon it, and set that the test succeeded if we're able
+             */
+            public Boolean call()
+            {
+               try
+               {
+                  // Create a new session, which should be allowed during passivation
+                  MyStatefulLocal bean2 = (MyStatefulLocal) new InitialContext().lookup(sfsbJndiName);
+
+                  // If we've got nothing
+                  if (bean2 == null)
+                  {
+                     // Let test fail, logging along the way
+                     log.error("Lookup did not succeed");
+                  }
+
+                  // Invoke upon the new Session
+                  int next = bean2.getNextCounter();
+
+                  // Test the value
+                  if (next == 0)
+                  {
+                     return true;
+                  }
+
+                  // Value was not expected, let the test fail and log
+                  log.error("Invocation succeeded, but expected next counter of 0 and got: " + next);
+
+               }
+               // We can't fail the unit test from here, so log the error
+               catch (Exception e)
+               {
+                  log.error("Test encountered an error", e);
+               }
+
+               // Fail, should have gotten expected value and returned above
+               return false;
+            }
+         };
+
+         // Run the Test
+         runTestDuringPassivation(invokeDuringPasssivationTest);         
+      }
+      catch(Throwable t)
+      {
+         log.error("Throwable thrown", t);
+         throw t;
+      }
+      finally
+      {
+         log.info("Resetting all barriers and clearing the cache...");
+         resetAndClear();
+      }
+   }
+
+   // --------------------------------------------------------------------------------||
+   // Internal Helper Methods --------------------------------------------------------||
+   // --------------------------------------------------------------------------------||
+
+   /**
+    * Performs the following common test actions:
+    * 
+    * 1) Looks up and invokes upon a new SFSB instance
+    * 2) Blocks passivation from completing
+    * 3) Waits for the SFSB instance to becomes eligible for passivation
+    * 4) Forces passivation to run
+    * 5) Runs the specified Test Thread
+    * 6) Allows the test to complete either successfully or unsuccessfully, or times out if stuck
+    * 7) Reports failure or success overall
+    */
+   protected void runTestDuringPassivation(Callable<Boolean> testThread) throws Throwable
+   {
+      // Initialize
+      final String sfsbJndiName = MyStatefulLocal.JNDI_NAME;
+      boolean testSucceeded = false;
+
+      // Lookup an instance
+      MyStatefulLocal bean = (MyStatefulLocal) new InitialContext().lookup(sfsbJndiName);
+
+      // Invoke upon our bean
+      int next = bean.getNextCounter();
+      log.info("Got counter from bean1 : " + next);
+      TestCase.assertEquals("SFSB did not return expected next counter", 0, next);
+
+      // Get our bean's Session ID
+      Object sessionId = this.getSessionId(bean);
+
+      // Get the lock to block the PM, now
+      boolean gotLock = pmPassivationLockTryLock();
+
+      // Once PM lock is acquired, everything is in "try" so we release in "finally"
+      try
+      {
+         // Ensure we got the PM lock, else fail the test
+         TestCase.assertTrue("Test was not able to immediately get the lock to block the PersistenceManager", gotLock);
+         log.info("Locked " + BlockingPersistenceManager.class.getSimpleName());
+
+         /*
+          * Mark our session as expired
+          */
+
+         // Mark
+         makeSessionEligibleForPassivation(sessionId);
+
+         /*
+          * Passivate
+          */
+
+         // Trigger Passivation
+         forcePassivation();
+         log.info("Passivation forced, carrying out test");
+
+         prePassivateBarrierAwait(5, TimeUnit.SECONDS);
+
+         // Block until the PM is ready to passivate
+         log.info("Waiting on common barrier for PM to run...");
+         pmBarrierAwait(5, TimeUnit.SECONDS);
+         log.info("PM and test have met barrier, passivation running (but will be blocked to complete by test)");
+
+         /*
+          * At this point, we've told the passivation Thread to start, and have 
+          * locked it from completing.  So let's try our test in another Thread
+          * so we can detect a deadlock or permanent blocking after a timeout
+          */
+         ExecutorService executor = Executors.newFixedThreadPool(1);
+         Future<Boolean> futureResult = executor.submit(testThread);
+         boolean result = futureResult.get(5, TimeUnit.SECONDS);
+
+         /*
+          * Assert on the result
+          */
+
+         // If the test has not succeeded
+         if (!result)
+         {
+            // Fail
+            TestCase.fail("The test has completed without success");
+         }
+
+         // If the test has been successful
+         else
+         {
+            log.info("Test Succeeded");
+            testSucceeded = true;
+         }
+      }
+      finally
+      {
+
+         // Allow the Persistence Manager to finish up
+         log.info("Letting the PM perform passivation...");
+         pmPassivationLockUnlock();
+      }
+
+      // We need to allow time to let the Cache finish passivation, so block until it's done
+      log.info("Waiting on Cache to tell us passivation is completed...");
+      postPassivateBarrierAwait(5, TimeUnit.SECONDS);
+      log.info("Test sees Cache reports passivation completed.");
+
+      /*
+       * Here we ensure that the session was removed from the internal cacheMap
+       */
+      boolean beanIsInCache = doesCacheMapContainKey(sessionId);
+      TestCase.assertFalse("bean " + sessionId + " was not removed from cache", beanIsInCache);
+
+      // Ensure we're good
+      TestCase.assertTrue("The test did not succeed", testSucceeded);
+   }
+   
+   private Object getSessionId(MyStatefulLocal bean)
+   {
+      StatefulLocalProxy handler = (StatefulLocalProxy) Proxy.getInvocationHandler(bean);
+      Object sessionId = handler.getId();
+      return sessionId;      
+   }
+
+}

Added: branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/MyStatefulCacheProxyRemote.java
===================================================================
--- branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/MyStatefulCacheProxyRemote.java	                        (rev 0)
+++ branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/MyStatefulCacheProxyRemote.java	2009-02-06 16:44:38 UTC (rev 83968)
@@ -0,0 +1,42 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.ejb3.test.ejbthree1549;
+
+/**
+ * MyStatefulCacheProxyRemote.
+ * 
+ * @author <a href="mailto:galder.zamarreno at jboss.com">Galder Zamarreno</a>
+ */
+public interface MyStatefulCacheProxyRemote
+{
+   String JNDI_NAME = "MyStatefulCacheProxyRemote/remote";
+      
+   void testSessionRemovalDuringPassivation() throws Throwable;
+   
+   void testInvokeSameSessionDuringPassivation() throws Throwable;
+   
+   void testInvokeSameSessionDuringPrePassivation() throws Throwable;
+
+   void testNewSessionMayBeCreatedDuringPassivation() throws Throwable;
+
+   void testSessionMayBeInvokedWhileAnotherIsPassivating() throws Throwable;
+}

Added: branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/MyStatefulLocal.java
===================================================================
--- branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/MyStatefulLocal.java	                        (rev 0)
+++ branches/JBPAPP_4_3_0_GA_JBPAPP-1671/ejb3/src/test/org/jboss/ejb3/test/ejbthree1549/MyStatefulLocal.java	2009-02-06 16:44:38 UTC (rev 83968)
@@ -0,0 +1,45 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.ejb3.test.ejbthree1549;
+
+/**
+ * MyStatefulLocal.
+ * 
+ * @author <a href="mailto:galder.zamarreno at jboss.com">Galder Zamarreno</a>
+ */
+public interface MyStatefulLocal
+{
+   String JNDI_NAME = "MyStatefulBean/local";
+
+   int PASSIVATION_TIMEOUT = 1;
+
+   int REMOVAL_TIMEOUT = 100;
+
+   // --------------------------------------------------------------------------------||
+   // Contracts ----------------------------------------------------------------------||
+   // --------------------------------------------------------------------------------||
+
+   /**
+    * Returns and increments the counter, which starts at 0
+    */
+   int getNextCounter();
+}




More information about the jboss-cvs-commits mailing list