Java tutorial
/** * Copyright 2003-2007 Luck Consulting Pty Ltd * * Licensed 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 net.sf.ehcache.constructs.blocking; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; import net.sf.ehcache.CacheTest; import net.sf.ehcache.Ehcache; import net.sf.ehcache.Element; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Test cases for the {@link SelfPopulatingCache}. * * @author Adam Murdoch * @author Greg Luck * @version $Id: SelfPopulatingCacheTest.java 608 2008-05-04 03:32:51Z gregluck $ */ public class SelfPopulatingCacheTest extends CacheTest { private static final Log LOG = LogFactory.getLog(SelfPopulatingCache.class.getName()); /** * Shared with subclass */ protected CacheManager manager; /** * Shared with subclass */ protected SelfPopulatingCache selfPopulatingCache; /** * Shared with subclass */ protected Ehcache cache; /** * Number of factory requests */ protected volatile int cacheEntryFactoryRequests; /** * Load up the test cache */ protected void setUp() throws Exception { super.setUp(); manager = new CacheManager(); cache = manager.getCache("sampleIdlingExpiringCache"); selfPopulatingCache = new SelfPopulatingCache(cache, new CountingCacheEntryFactory("value")); cacheEntryFactoryRequests = 0; } /** * teardown */ protected void tearDown() throws Exception { selfPopulatingCache.removeAll(); manager.shutdown(); super.tearDown(); } /** * Tests fetching an entry. */ public void testFetch() throws Exception { // Lookup final Element element = selfPopulatingCache.get("key"); assertEquals("value", element.getValue()); } /** * Tests fetching an unknown entry. */ public void testFetchUnknown() throws Exception { final CacheEntryFactory factory = new CountingCacheEntryFactory(null); selfPopulatingCache = new SelfPopulatingCache(cache, factory); // Lookup assertNull(cache.get("key")); } /** * Tests when fetch fails. */ public void testFetchFail() throws Exception { final Exception exception = new Exception("Failed."); final CacheEntryFactory factory = new CacheEntryFactory() { public Object createEntry(final Object key) throws Exception { throw exception; } }; selfPopulatingCache = new SelfPopulatingCache(cache, factory); // Lookup try { selfPopulatingCache.get("key"); fail(); } catch (final Exception e) { Thread.sleep(20); // Check the error assertEquals("Could not fetch object for cache entry with key \"key\".", e.getMessage()); } } /** * Tests that an entry is created once only. */ public void testCreateOnce() throws Exception { final String value = "value"; final CountingCacheEntryFactory factory = new CountingCacheEntryFactory(value); selfPopulatingCache = new SelfPopulatingCache(cache, factory); // Fetch the value several times for (int i = 0; i < 5; i++) { assertSame(value, selfPopulatingCache.get("key").getObjectValue()); assertEquals(1, factory.getCount()); } } /** * Tests refreshing the entries. */ public void testRefresh() throws Exception { final String value = "value"; final CountingCacheEntryFactory factory = new CountingCacheEntryFactory(value); selfPopulatingCache = new SelfPopulatingCache(cache, factory); // Check the value assertSame(value, selfPopulatingCache.get("key").getObjectValue()); assertEquals(1, factory.getCount()); // Refresh selfPopulatingCache.refresh(); assertEquals(2, factory.getCount()); // Check the value assertSame(value, selfPopulatingCache.get("key").getObjectValue()); assertEquals(2, factory.getCount()); } /** * Tests that the current thread, which gets renamed when it enters a SelfPopulatingCache, comes out with * its old name. */ public void testThreadNaming() throws Exception { final String value = "value"; final CountingCacheEntryFactory factory = new CountingCacheEntryFactory(value); selfPopulatingCache = new SelfPopulatingCache(cache, factory); String originalThreadName = Thread.currentThread().getName(); // Check the value selfPopulatingCache.get("key"); assertEquals(originalThreadName, Thread.currentThread().getName()); // Refresh selfPopulatingCache.refresh(); assertEquals(originalThreadName, Thread.currentThread().getName()); // Check the value with null key selfPopulatingCache.get(null); assertEquals(originalThreadName, Thread.currentThread().getName()); } /** * Tests discarding little used entries. * <cache name="sampleIdlingExpiringCache" * maxElementsInMemory="1" * eternal="false" * timeToIdleSeconds="2" * timeToLiveSeconds="5" * overflowToDisk="true" * /> */ public void testDiscardLittleUsed() throws Exception { final CacheEntryFactory factory = new CountingCacheEntryFactory("value"); selfPopulatingCache = new SelfPopulatingCache(cache, factory); selfPopulatingCache.get("key1"); selfPopulatingCache.get("key2"); assertEquals(2, selfPopulatingCache.getSize()); selfPopulatingCache.refresh(); assertEquals(2, selfPopulatingCache.getSize()); Thread.sleep(2020); //Will be two, because counting expired elements assertEquals(2, selfPopulatingCache.getSize()); // Check the cache selfPopulatingCache.removeAll(); assertEquals(0, selfPopulatingCache.getSize()); } /** * Tests discarding little used entries, where refreshing is slow. * <cache name="sampleIdlingExpiringCache" * maxElementsInMemory="1" * eternal="false" * timeToIdleSeconds="2" * timeToLiveSeconds="5" * overflowToDisk="true" * /> */ public void testDiscardLittleUsedSlow() throws Exception { final CacheEntryFactory factory = new CacheEntryFactory() { public Object createEntry(final Object key) throws Exception { Thread.sleep(200); return key; } }; selfPopulatingCache = new SelfPopulatingCache(cache, factory); } /** * Expected: If multiple threads try to retrieve the same key from a * SelfPopulatingCache at the same time, and that key is not yet in the cache, * one thread obtains the lock for that key and uses the CacheEntryFactory to * generate the cache entry and all other threads wait on the lock. * Any and all threads which timeout while waiting for this lock should fail * to acquire the lock for that key and throw an exception. * <p/> * This thread tests for this by having several threads to a cache "get" for * the same key, allowing one to acquire the lock and the others to wait. The * one that acquires the lock and attempts to generate the cache entry for the * key waits for a period of time long enough to allow all other threads to * timeout waiting for the lock. Any thread that succeeds in acquiring the lock, * including the first to do so, increment a counter when they begin creating * the cache entry using the CacheEntryFactory. It is expected that this * counter will only be "1" after all threads complete since all but the * first to acquire it should timeout and throw exceptions. * <p/> * We then test that a thread that comes along later increments the counter. */ public void testSelfPopulatingBlocksWithTimeoutSetNull() throws InterruptedException { selfPopulatingCache = new SelfPopulatingCache(new Cache("TestCache", 50, false, false, 0, 0), new NullCachePopulator()); selfPopulatingCache.setTimeoutMillis(200); manager.addCache(selfPopulatingCache); CacheAccessorThread[] cacheAccessorThreads = new CacheAccessorThread[10]; for (int i = 0; i < cacheAccessorThreads.length; i++) { cacheAccessorThreads[i] = new CacheAccessorThread(selfPopulatingCache, "key1"); cacheAccessorThreads[i].start(); // Do a slight delay here so that all the timeouts // don't happen simultaneously - this is key try { Thread.sleep(20); } catch (InterruptedException ignored) { // } } //All of the others should have timed out. The first thread will have returned null. // This thread should be able to have a go, thus setting the count to 2 Thread.sleep(1000); Thread lateThread = new CacheAccessorThread(selfPopulatingCache, "key1"); lateThread.start(); lateThread.join(); assertEquals("Too many cacheAccessorThreads tried to create selfPopulatingCache entry for key1", 2, cacheEntryFactoryRequests); } /** * Creating 11 Threads which attempt to get a null entry will result, eventually, in 11 * calls to the CacheEntryFactory * @throws InterruptedException */ public void testSelfPopulatingBlocksWithoutTimeoutSetNull() throws InterruptedException { selfPopulatingCache = new SelfPopulatingCache(new Cache("TestCache", 50, false, false, 0, 0), new NullCachePopulator()); //selfPopulatingCache.setTimeoutMillis(200); manager.addCache(selfPopulatingCache); CacheAccessorThread[] cacheAccessorThreads = new CacheAccessorThread[10]; for (int i = 0; i < cacheAccessorThreads.length; i++) { cacheAccessorThreads[i] = new CacheAccessorThread(selfPopulatingCache, "key1"); cacheAccessorThreads[i].start(); // Do a slight delay here so that all the timeouts // don't happen simultaneously - this is key try { Thread.sleep(20); } catch (InterruptedException ignored) { // } } //All of the others should have timed out. The first thread will have returned null. // This thread should be able to have a go, thus setting the count to 2 Thread.sleep(12000); Thread lateThread = new CacheAccessorThread(selfPopulatingCache, "key1"); lateThread.start(); lateThread.join(); assertEquals("The wrong number of cacheAccessorThreads tried to create selfPopulatingCache entry for key1", 11, cacheEntryFactoryRequests); } /** * Creating 11 Threads which attempt to get a non-null entry will result in 1 * call to the CacheEntryFactory * @throws InterruptedException */ public void testSelfPopulatingBlocksWithoutTimeoutSetNonNull() throws InterruptedException { selfPopulatingCache = new SelfPopulatingCache(new Cache("TestCache", 50, false, false, 0, 0), new NonNullCachePopulator()); //selfPopulatingCache.setTimeoutMillis(200); manager.addCache(selfPopulatingCache); CacheAccessorThread[] cacheAccessorThreads = new CacheAccessorThread[10]; for (int i = 0; i < cacheAccessorThreads.length; i++) { cacheAccessorThreads[i] = new CacheAccessorThread(selfPopulatingCache, "key1"); cacheAccessorThreads[i].start(); // Do a slight delay here so that all the timeouts // don't happen simultaneously - this is key try { Thread.sleep(20); } catch (InterruptedException ignored) { // } } //All of the others should have timed out. The first thread will have returned null. // This thread should be able to have a go, thus setting the count to 2 Thread.sleep(2000); Thread lateThread = new CacheAccessorThread(selfPopulatingCache, "key1"); lateThread.start(); lateThread.join(); assertEquals("The wrong number of cacheAccessorThreads tried to create selfPopulatingCache entry for key1", 1, cacheEntryFactoryRequests); } /** * A thread that accesses a selfpopulating cache */ private final class CacheAccessorThread extends Thread { private Ehcache cache; private String key; private CacheAccessorThread(Ehcache cache, String key) { this.cache = cache; this.key = key; } /** * Thread run method */ public void run() { try { cache.get(key); } catch (Exception e) { LOG.info("Exception: " + e.getMessage()); } } } /** * A cache entry factory that sleeps beyond the lock timeout */ private class NullCachePopulator implements CacheEntryFactory { public Object createEntry(Object key) throws Exception { cacheEntryFactoryRequests++; Thread.sleep(1000); return null; } } /** * A cache entry factory that sleeps beyond the lock timeout */ private class NonNullCachePopulator implements CacheEntryFactory { public Object createEntry(Object key) throws Exception { cacheEntryFactoryRequests++; Thread.sleep(1000); return "value"; } } /** * Shows the effect of jamming large amounts of puts into a cache that overflows to disk. * The DiskStore should cause puts to back off and avoid an out of memory error. */ public void testBehaviourOnDiskStoreBackUp() throws Exception { Cache cache = new Cache("testGetMemoryStoreSize", 10, true, false, 100, 200, false, 0); manager.addCache(cache); assertEquals(0, cache.getMemoryStoreSize()); Element a = null; int i = 0; try { for (; i < 200000; i++) { String key = i + ""; String value = key; a = new Element(key, value + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"); cache.put(a); } } catch (OutOfMemoryError e) { //the disk store backs up on the laptop. LOG.info("OutOfMemoryError: " + e.getMessage() + " " + i); fail(); } } }