1   /**
2    *  Copyright 2003-2007 Greg Luck
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  
17  package net.sf.ehcache.constructs.blocking;
18  
19  import net.sf.ehcache.CacheException;
20  import net.sf.ehcache.StopWatch;
21  import net.sf.ehcache.Element;
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  
25  import java.io.Serializable;
26  import java.util.ArrayList;
27  import java.util.List;
28  
29  
30  /**
31   * Test cases for the {@link UpdatingSelfPopulatingCache}.
32   *
33   * @author <a href="mailto:gluck@thoughtworks.com">Greg Luck</a>
34   * @version $Id: UpdatingSelfPopulatingCacheTest.java 512 2007-07-10 09:18:45Z gregluck $
35   */
36  public class UpdatingSelfPopulatingCacheTest extends SelfPopulatingCacheTest {
37      private static final Log LOG = LogFactory.getLog(UpdatingSelfPopulatingCacheTest.class.getName());
38  
39      /**
40       * Tests fetching an entry, and then an update.
41       */
42      public void testFetchAndUpdate() throws Exception {
43          final String value = "value";
44          final CountingCacheEntryFactory factory = new CountingCacheEntryFactory(value);
45          selfPopulatingCache = new UpdatingSelfPopulatingCache(cache, factory);
46  
47  
48          //test null
49          Element element = selfPopulatingCache.get(null);
50  
51          // Lookup
52          element = selfPopulatingCache.get("key");
53          assertSame(value, element.getObjectValue());
54          assertEquals(2, factory.getCount());
55  
56          Object actualValue = selfPopulatingCache.get("key").getObjectValue();
57          assertSame(value, actualValue);
58          assertEquals(3, factory.getCount());
59  
60          actualValue = selfPopulatingCache.get("key").getObjectValue();
61          assertSame(value, actualValue);
62          assertEquals(4, factory.getCount());
63      }
64  
65      /**
66       * Tests when fetch fails.
67       */
68      public void testFetchFail() throws Exception {
69          final Exception exception = new Exception("Failed.");
70          final UpdatingCacheEntryFactory factory = new UpdatingCacheEntryFactory() {
71              public Object createEntry(final Object key)
72                      throws Exception {
73                  throw exception;
74              }
75  
76              public void updateEntryValue(Object key, Object value)
77                      throws Exception {
78                  throw exception;
79              }
80          };
81  
82          selfPopulatingCache = new UpdatingSelfPopulatingCache(cache, factory);
83  
84          // Lookup
85          try {
86              selfPopulatingCache.get("key");
87              fail();
88          } catch (final Exception e) {
89              Thread.sleep(20);
90  
91              // Check the error
92              assertEquals("Could not fetch object for cache entry \"key\".", e.getMessage());
93          }
94  
95      }
96  
97      /**
98       * Tests refreshing the entries.
99       */
100     public void testRefresh() throws Exception {
101         final String value = "value";
102         final CountingCacheEntryFactory factory = new CountingCacheEntryFactory(value);
103         selfPopulatingCache = new UpdatingSelfPopulatingCache(cache, factory);
104 
105         // Refresh
106         try {
107             selfPopulatingCache.refresh();
108             fail();
109         } catch (CacheException e) {
110             //expected.
111             assertEquals("UpdatingSelfPopulatingCache objects should not be refreshed.", e.getMessage());
112         }
113 
114     }
115 
116     /**
117      * Thrashes a UpdatingSelfPopulatingCache and looks for liveness problems
118      * Note. These timings are without logging. Turn logging off to run this test.
119      * <p/>
120      * To get this test to fail, add the synchronized keyword to {@link UpdatingSelfPopulatingCache#get(java.io.Serializable)}.
121      */
122     public void testThrashUpdatingSelfPopulatingCache() throws Exception {
123         final String value = "value";
124         final CountingCacheEntryFactory factory = new CountingCacheEntryFactory(value);
125         selfPopulatingCache = new UpdatingSelfPopulatingCache(cache, factory);
126         long duration = thrashCache((UpdatingSelfPopulatingCache) selfPopulatingCache, 300L, 1500L);
127         LOG.debug("Thrash Duration:" + duration);
128     }
129 
130     /**
131      * This method tries to get the cache to slow up.
132      * It creates 40 threads, does blocking gets and monitors the liveness right the way through
133      */
134     private long thrashCache(final UpdatingSelfPopulatingCache cache, final long liveness, final long retrievalTime)
135             throws Exception {
136         StopWatch stopWatch = new StopWatch();
137 
138         // Create threads that do gets
139         final List executables = new ArrayList();
140         for (int i = 0; i < 10; i++) {
141             final UpdatingSelfPopulatingCacheTest.Executable executable = new UpdatingSelfPopulatingCacheTest.Executable() {
142                 public void execute() throws Exception {
143                     for (int i = 0; i < 10; i++) {
144                         final String key = "key" + i;
145                         Object value = cache.get(key);
146                         checkLiveness(cache, liveness);
147                         if (value == null) {
148                             cache.put(new Element(key, "value" + i));
149                         }
150                         //The key will be in. Now check we can get it quickly
151                         checkRetrievalOnKnownKey(cache, retrievalTime, key);
152                     }
153                 }
154             };
155             executables.add(executable);
156         }
157 
158         runThreads(executables);
159         cache.removeAll();
160         return stopWatch.getElapsedTime();
161     }
162 
163     /**
164      * Checks that the liveness method returns in less than a given amount of time.
165      * liveness() is a method that simply returns a String. It should be very fast. It can be
166      * delayed because it is a synchronized method, and must acquire an object lock before continuing
167      * The old blocking cache was taking up to several minutes in production
168      *
169      * @param cache a BlockingCache
170      */
171     private void checkLiveness(UpdatingSelfPopulatingCache cache, long liveness) {
172         StopWatch stopWatch = new StopWatch();
173         cache.liveness();
174         long measuredLiveness = stopWatch.getElapsedTime();
175         assertTrue("liveness is " + measuredLiveness + " but should be less than " + liveness + "ms",
176                 measuredLiveness < liveness);
177     }
178 
179     /**
180      * Checks that the liveness method returns in less than a given amount of time.
181      * liveness() is a method that simply returns a String. It should be very fast. It can be
182      * delayed because it is a synchronized method, and must acquire
183      * an object lock before continuing. The old blocking cache was taking up to several minutes in production
184      *
185      * @param cache a BlockingCache
186      */
187     private void checkRetrievalOnKnownKey(UpdatingSelfPopulatingCache cache, long requiredRetrievalTime, Serializable key)
188             throws LockTimeoutException {
189         StopWatch stopWatch = new StopWatch();
190         cache.get(key);
191         long measuredRetrievalTime = stopWatch.getElapsedTime();
192         assertTrue("Retrieval time on known key is " + measuredRetrievalTime
193                 + " but should be less than " + requiredRetrievalTime + "ms",
194                 measuredRetrievalTime < requiredRetrievalTime);
195     }
196 
197 
198 }