[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