You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by ga...@apache.org on 2010/08/23 20:58:18 UTC

svn commit: r988248 - in /openejb/trunk/openejb3/container/openejb-core/src: main/java/org/apache/openejb/core/stateful/ test/java/org/apache/openejb/core/stateful/

Author: gawor
Date: Mon Aug 23 18:58:17 2010
New Revision: 988248

URL: http://svn.apache.org/viewvc?rev=988248&view=rev
Log:
OPENEJB-1146: Support for stateful timeout. Periodically check for expired beans. Based on patch from Thiago Veronezi.

Added:
    openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulTimeoutTest.java   (with props)
Modified:
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/Cache.java
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/Instance.java
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/SimpleCache.java
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/StatefulContainerFactory.java
    openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/Compat3to2Test.java
    openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulContainerTest.java
    openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulSessionBeanTest.java

Modified: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/Cache.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/Cache.java?rev=988248&r1=988247&r2=988248&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/Cache.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/Cache.java Mon Aug 23 18:58:17 2010
@@ -17,6 +17,8 @@
  */
 package org.apache.openejb.core.stateful;
 
+import org.apache.openejb.util.Duration;
+
 public interface Cache<K, V> {
     /**
      * Gets the listener for cache events.
@@ -60,9 +62,19 @@ public interface Cache<K, V> {
     V remove(K key);
 
     /**
-     * Removes all of th entries that match the specified filter.
+     * Removes all of the entries that match the specified filter.
      */
     void removeAll(CacheFilter<V> filter);
+    
+    /**
+     * Initialize the cache.
+     */
+    void init();
+    
+    /**
+     * Destroy the cache.
+     */
+    void destroy();
 
     /**
      * Callback listener for cache events.
@@ -99,4 +111,14 @@ public interface Cache<K, V> {
          */
         boolean matches(V v);
     }
+    
+    /**
+     * A cache entry can implement this interface
+     * to provide its timeout.     
+     */
+    public interface TimeOut {
+        
+        Duration getTimeOut();
+        
+    }
 }

Modified: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/Instance.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/Instance.java?rev=988248&r1=988247&r2=988248&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/Instance.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/Instance.java Mon Aug 23 18:58:17 2010
@@ -32,10 +32,11 @@ import org.apache.openejb.core.CoreDeplo
 import org.apache.openejb.core.transaction.BeanTransactionPolicy.SuspendedTransaction;
 import org.apache.openejb.loader.SystemInstance;
 import org.apache.openejb.spi.ContainerSystem;
+import org.apache.openejb.util.Duration;
 import org.apache.openejb.util.Index;
 import org.apache.openejb.util.PojoSerialization;
 
-public class Instance implements Serializable {
+public class Instance implements Serializable, Cache.TimeOut {
     private static final long serialVersionUID = 2862563626506556542L;
     public final CoreDeploymentInfo deploymentInfo;
     public final Object primaryKey;
@@ -73,6 +74,10 @@ public class Instance implements Seriali
         this.entityManagerArray = entityManagerArray;
     }
 
+    public Duration getTimeOut() {
+        return deploymentInfo.getStatefulTimeout();
+    }
+    
     public synchronized boolean isInUse() {
         return inUse;
     }

Modified: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/SimpleCache.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/SimpleCache.java?rev=988248&r1=988247&r2=988248&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/SimpleCache.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/SimpleCache.java Mon Aug 23 18:58:17 2010
@@ -24,7 +24,11 @@ import java.util.List;
 import java.util.Map;
 import java.util.Queue;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.ReentrantLock;
 
@@ -69,9 +73,20 @@ public class SimpleCache<K, V> implement
     /**
      * A bean may be destroyed if it isn't used in this length of time (in
      * milliseconds).
+     * A time out of value -1 means a bean will never be destroyed due to time out.
+     * A time out of value 0 means a bean can be immediately destroyed.
      */
-    private long timeOut;
+    private long timeOut = -1;
 
+    private ScheduledExecutorService executor;
+    
+    /**
+     * Specifies how often the cache is checked for timed out beans.
+     */
+    private long frequency = 60 * 1000;
+    
+    private ScheduledFuture future;
+    
     public SimpleCache() {
     }
 
@@ -83,6 +98,36 @@ public class SimpleCache<K, V> implement
         this.timeOut = timeOut.getTime(TimeUnit.MILLISECONDS);
     }
 
+    public synchronized void init() {
+        if (frequency > 0 && future == null) {
+            initScheduledExecutorService();
+        
+            future = executor.scheduleWithFixedDelay(new Runnable() {
+                         public void run() {     
+                             processLRU();
+                         }               
+                     }, frequency, frequency, TimeUnit.MILLISECONDS);
+        }
+    }
+    
+    public synchronized void destroy() {    
+        if (future != null) {
+            future.cancel(false);
+        }
+    }
+    
+    private synchronized void initScheduledExecutorService() {
+        if (executor == null) {
+            executor = Executors.newScheduledThreadPool(1, new ThreadFactory() {
+                public Thread newThread(Runnable runable) {
+                    Thread t = new Thread(runable, "Stateful cache");
+                    t.setDaemon(true);
+                    return t;
+                }
+            });               
+        }
+    }
+    
     public synchronized CacheListener<V> getListener() {
         return listener;
     }
@@ -132,6 +177,22 @@ public class SimpleCache<K, V> implement
         this.timeOut = timeOut * 60 * 1000;
     }
 
+    public void setScheduledExecutorService(ScheduledExecutorService executor) {
+        this.executor = executor;
+    }
+    
+    public ScheduledExecutorService getScheduledExecutorService() {
+        return executor;
+    }
+    
+    public void setFrequency(long frequency) {
+        this.frequency = frequency * 1000;
+    }
+    
+    public long getFrequency() {
+        return frequency;
+    }
+    
     public void add(K key, V value) {
         // find the existing entry
         Entry entry = cache.get(key);
@@ -183,7 +244,7 @@ public class SimpleCache<K, V> implement
                         // Entry has been removed between get and lock (most likely by undeploying the EJB), simply drop the instance
                         return null;
                 }
-
+                
                 // mark entry as in-use
                 entry.setState(EntryState.CHECKED_OUT);
 
@@ -236,7 +297,9 @@ public class SimpleCache<K, V> implement
             entry.lock.unlock();
         }
 
-        processLRU();
+        if (frequency == 0) {
+            processLRU();
+        }
     }
 
     public V remove(K key) {
@@ -291,6 +354,8 @@ public class SimpleCache<K, V> implement
         CacheListener<V> listener = this.getListener();
 
         // check for timed out entries
+        // go through all lru entries since even though entries are in
+        // least recently used order they might have different timeouts.
         Iterator<Entry> iterator = lru.iterator();
         while (iterator.hasNext()) {
             Entry entry = iterator.next();
@@ -326,10 +391,6 @@ public class SimpleCache<K, V> implement
                             logger.error("An unexpected exception occured from timedOut callback", e);
                         }
                     }
-                } else {
-                    // entries are in order of last updates, so if this bean isn't timed out
-                    // no further entries will be timed out
-                    break;
                 }
             } finally {
                 entry.lock.unlock();
@@ -380,7 +441,7 @@ public class SimpleCache<K, V> implement
                     // there is a race condition where the item could get added back into the lru
                     lru.remove(entry);
 
-                    // if the entry is actually timed out we just destroy it; othewise it is written to disk
+                    // if the entry is actually timed out we just destroy it; otherwise it is written to disk
                     if (entry.isTimedOut()) {
                         entry.setState(EntryState.REMOVED);
                         if (listener != null) {
@@ -480,12 +541,21 @@ public class SimpleCache<K, V> implement
         private final ReentrantLock lock = new ReentrantLock();
         private EntryState state;
         private long lastAccess;
+        private long timeOut;
 
         private Entry(K key, V value, EntryState state) {
             this.key = key;
             this.value = value;
             this.state = state;
-            lastAccess = System.currentTimeMillis();
+            
+            if (value instanceof Cache.TimeOut) {
+                Duration duration = ((Cache.TimeOut) value).getTimeOut();
+                this.timeOut = (duration != null) ? duration.getTime(TimeUnit.MILLISECONDS) : getTimeOut(); 
+            } else {
+                this.timeOut = getTimeOut();
+            }
+            
+            lastAccess = System.currentTimeMillis();                                 
         }
 
         private K getKey() {
@@ -511,18 +581,20 @@ public class SimpleCache<K, V> implement
         private boolean isTimedOut() {
             assertLockHeld();
 
-            long timeOut = getTimeOut();
-            if (timeOut == 0) {
+            if (timeOut < 0) {
                 return false;
+            } else if (timeOut == 0) {
+                return true;
+            } else {
+                long now = System.currentTimeMillis();
+                return (now - lastAccess) > timeOut;
             }
-            long now = System.currentTimeMillis();
-            return (now - lastAccess) > timeOut;
         }
 
         private void resetTimeOut() {
             assertLockHeld();
 
-            if (getTimeOut() > 0) {
+            if (timeOut > 0) {
                 lastAccess = System.currentTimeMillis();
             }
         }
@@ -533,4 +605,5 @@ public class SimpleCache<K, V> implement
             }
         }
     }
+    
 }

Modified: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/StatefulContainerFactory.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/StatefulContainerFactory.java?rev=988248&r1=988247&r2=988248&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/StatefulContainerFactory.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/StatefulContainerFactory.java Mon Aug 23 18:58:17 2010
@@ -85,6 +85,10 @@ public class StatefulContainerFactory {
         properties.put("BulkPassivate", s);
     }
 
+    public void setFrequency(String s) {
+        properties.put("Frequency", s);
+    }
+    
     public Properties getProperties() {
         return properties;
     }
@@ -98,6 +102,7 @@ public class StatefulContainerFactory {
         if (cache == null) {
             buildCache();
         }
+        cache.init();
         return new StatefulContainer(id, securityService, cache, accessTimeout);
     }
 

Modified: openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/Compat3to2Test.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/Compat3to2Test.java?rev=988248&r1=988247&r2=988248&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/Compat3to2Test.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/Compat3to2Test.java Mon Aug 23 18:58:17 2010
@@ -63,6 +63,7 @@ public class Compat3to2Test extends Test
         StatefulSessionContainerInfo statefulContainerInfo = config.configureService(StatefulSessionContainerInfo.class);
         statefulContainerInfo.properties.setProperty("PoolSize", "0");
         statefulContainerInfo.properties.setProperty("BulkPassivate", "1");
+        statefulContainerInfo.properties.setProperty("Frequency", "0");
         assembler.createContainer(statefulContainerInfo);
 
         EjbJar ejbJar = new EjbJar();

Modified: openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulContainerTest.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulContainerTest.java?rev=988248&r1=988247&r2=988248&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulContainerTest.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulContainerTest.java Mon Aug 23 18:58:17 2010
@@ -224,6 +224,7 @@ public class StatefulContainerTest exten
         StatefulSessionContainerInfo statefulContainerInfo = config.configureService(StatefulSessionContainerInfo.class);
         statefulContainerInfo.properties.setProperty("PoolSize", "0");
         statefulContainerInfo.properties.setProperty("BulkPassivate", "1");
+        statefulContainerInfo.properties.setProperty("Frequency", "0");
         assembler.createContainer(statefulContainerInfo);
 
         // Setup the descriptor information

Modified: openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulSessionBeanTest.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulSessionBeanTest.java?rev=988248&r1=988247&r2=988248&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulSessionBeanTest.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulSessionBeanTest.java Mon Aug 23 18:58:17 2010
@@ -59,6 +59,7 @@ public class StatefulSessionBeanTest ext
         StatefulSessionContainerInfo statefulContainerInfo = config.configureService(StatefulSessionContainerInfo.class);
         statefulContainerInfo.properties.setProperty("PoolSize", "0");
         statefulContainerInfo.properties.setProperty("BulkPassivate", "1");
+        statefulContainerInfo.properties.setProperty("Frequency", "0");
         assembler.createContainer(statefulContainerInfo);
 
         assembler.createApplication(config.configureApplication(buildTestApp()));

Added: openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulTimeoutTest.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulTimeoutTest.java?rev=988248&view=auto
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulTimeoutTest.java (added)
+++ openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulTimeoutTest.java Mon Aug 23 18:58:17 2010
@@ -0,0 +1,160 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.openejb.core.stateful;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.ejb.Local;
+import javax.ejb.NoSuchEJBException;
+import javax.ejb.Stateful;
+import javax.naming.InitialContext;
+
+import junit.framework.TestCase;
+
+import org.apache.openejb.assembler.classic.Assembler;
+import org.apache.openejb.assembler.classic.ProxyFactoryInfo;
+import org.apache.openejb.assembler.classic.SecurityServiceInfo;
+import org.apache.openejb.assembler.classic.StatefulSessionContainerInfo;
+import org.apache.openejb.assembler.classic.TransactionServiceInfo;
+import org.apache.openejb.client.LocalInitialContextFactory;
+import org.apache.openejb.config.ConfigurationFactory;
+import org.apache.openejb.jee.EjbJar;
+import org.apache.openejb.jee.StatefulBean;
+import org.apache.openejb.jee.Timeout;
+
+public class StatefulTimeoutTest extends TestCase {
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        System.setProperty(javax.naming.Context.INITIAL_CONTEXT_FACTORY,
+                LocalInitialContextFactory.class.getName());
+
+        final ConfigurationFactory config = new ConfigurationFactory();
+        final Assembler assembler = new Assembler();
+
+        assembler.createProxyFactory(config.configureService(ProxyFactoryInfo.class));
+        assembler.createTransactionManager(config.configureService(TransactionServiceInfo.class));
+        assembler.createSecurityService(config.configureService(SecurityServiceInfo.class));
+
+        final StatefulSessionContainerInfo statefulContainerInfo = config
+                .configureService(StatefulSessionContainerInfo.class);
+        statefulContainerInfo.properties.setProperty("BulkPassivate", "1");
+        // clear cache every 3 seconds
+        statefulContainerInfo.properties.setProperty("Frequency", "3");
+        assembler.createContainer(statefulContainerInfo);
+
+        EjbJar ejbJar = new EjbJar();
+        Timeout timeout;
+        
+        StatefulBean bean1 = new StatefulBean("BeanNegative", MyLocalBeanImpl.class);
+        timeout = new Timeout();
+        timeout.setTimeout(-1);
+        timeout.setUnit(TimeUnit.SECONDS);
+        bean1.setStatefulTimeout(timeout);
+        
+        StatefulBean bean0 = new StatefulBean("BeanZero", MyLocalBeanImpl.class);
+        timeout = new Timeout();
+        timeout.setTimeout(0);
+        timeout.setUnit(TimeUnit.SECONDS);
+        bean0.setStatefulTimeout(timeout);
+        
+        StatefulBean bean5 = new StatefulBean("Bean", MyLocalBeanImpl.class);
+        timeout = new Timeout();
+        timeout.setTimeout(5);
+        timeout.setUnit(TimeUnit.SECONDS);
+        bean5.setStatefulTimeout(timeout);       
+
+        ejbJar.addEnterpriseBean(bean1);
+        ejbJar.addEnterpriseBean(bean0);
+        ejbJar.addEnterpriseBean(bean5);
+
+        assembler.createApplication(config.configureApplication(ejbJar));
+    }
+
+    public void testZeroTimeout() throws Exception {
+        InitialContext ctx = new InitialContext();
+        MyLocalBean bean;
+
+        // cache is cleared ever 3 seconds and bean timeout is 0 seconds
+        bean = (MyLocalBean) ctx.lookup("BeanZeroLocal");        
+        bean.doNothing(0);
+        
+        // cache should be cleared by now and the bean should be removed
+        Thread.sleep(5 * 1000);        
+        try {
+            bean.doNothing(0);
+            fail("Did not throw expected exception");
+        } catch (NoSuchEJBException e) {
+            // that's what we expect
+        }
+    }
+    
+    public void testTimeout() throws Exception {
+        InitialContext ctx = new InitialContext();
+        MyLocalBean bean;
+
+        // cache is cleared ever 3 seconds and bean timeout is 5 seconds
+        bean = (MyLocalBean) ctx.lookup("BeanLocal");        
+        bean.doNothing(0);
+        
+        // cache should be cleared once by now but the bean is not expired yet
+        Thread.sleep(5 * 1000);        
+        bean.doNothing(0);
+        
+        // cache should be cleared again and our bean should be removed
+        // since the bean was idle for more than 5 seconds.
+        Thread.sleep(10 * 1000);        
+        try {
+            bean.doNothing(0);
+            fail("Did not throw expected exception");
+        } catch (NoSuchEJBException e) {
+            // that's what we expect
+        }
+    }
+    
+    public void testNegativeTimeout() throws Exception {
+        InitialContext ctx = new InitialContext();
+        MyLocalBean bean;
+
+        // cache is cleared ever 3 seconds and bean timeout is -1 seconds
+        bean = (MyLocalBean) ctx.lookup("BeanNegativeLocal");        
+        bean.doNothing(0);
+        
+        // cache should be cleared by now a few times but bean should remain
+        // available.
+        Thread.sleep(10 * 1000);        
+        bean.doNothing(0);
+    }
+
+    @Local
+    public static interface MyLocalBean {
+        void doNothing(long sleep);
+    }
+
+    @Stateful
+    public static class MyLocalBeanImpl implements MyLocalBean {
+        public void doNothing(long sleep) {
+            try {
+                Thread.sleep(sleep);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}

Propchange: openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulTimeoutTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulTimeoutTest.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/StatefulTimeoutTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain