View Javadoc
1   package org.apache.commons.jcs3;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.Serializable;
23  import java.util.ArrayList;
24  import java.util.List;
25  
26  import org.apache.commons.jcs3.access.CacheAccess;
27  import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
28  import org.apache.commons.jcs3.engine.stats.behavior.IStats;
29  import org.apache.commons.jcs3.log.Log;
30  import org.apache.commons.jcs3.log.LogManager;
31  
32  import junit.framework.TestCase;
33  
34  /**
35   * This is based on a test that was posted to the user's list:
36   * <p>
37   * http://www.opensubscriber.com/message/jcs-users@jakarta.apache.org/2435965.html
38   */
39  public class JCSThrashTest
40      extends TestCase
41  {
42      /** The logger. */
43      private static final Log LOG = LogManager.getLog( JCSThrashTest.class.getName() );
44  
45      /**
46       * the cache instance
47       */
48      protected CacheAccess<String, Serializable> jcs;
49  
50      /**
51       * Sets up the test
52       * @throws Exception
53       */
54      @Override
55      protected void setUp()
56          throws Exception
57      {
58          super.setUp();
59          JCS.setConfigFilename( "/TestThrash.ccf" );
60          jcs = JCS.getInstance( "testcache" );
61      }
62  
63      /**
64       * @throws Exception
65       */
66      @Override
67      protected void tearDown()
68          throws Exception
69      {
70          super.tearDown();
71          jcs.clear();
72          jcs.dispose();
73      }
74  
75      /**
76       * Tests adding an entry.
77       * @throws Exception
78       */
79      public void testPut()
80          throws Exception
81      {
82          final String value = "value";
83          final String key = "key";
84  
85          // Make sure the element is not found
86          assertEquals( 0, getListSize() );
87  
88          assertNull( jcs.get( key ) );
89  
90          jcs.put( key, value );
91  
92          // Get the element
93          LOG.info( "jcs.getStats(): " + jcs.getStatistics() );
94          assertEquals( 1, getListSize() );
95          assertNotNull( jcs.get( key ) );
96          assertEquals( value, jcs.get( key ) );
97      }
98  
99      /**
100      * Test elements can be removed from the store
101      * @throws Exception
102      */
103     public void testRemove()
104         throws Exception
105     {
106         jcs.put( "key1", "value1" );
107         assertEquals( 1, getListSize() );
108 
109         jcs.remove( "key1" );
110         assertEquals( 0, getListSize() );
111 
112         jcs.put( "key2", "value2" );
113         jcs.put( "key3", "value3" );
114         assertEquals( 2, getListSize() );
115 
116         jcs.remove( "key2" );
117         assertEquals( 1, getListSize() );
118 
119         // Try to remove an object that is not there in the store
120         jcs.remove( "key4" );
121         assertEquals( 1, getListSize() );
122     }
123 
124     /**
125      * This does a bunch of work and then verifies that the memory has not grown by much. Most of
126      * the time the amount of memory used after the test is less.
127      * @throws Exception
128      */
129     public void testForMemoryLeaks()
130         throws Exception
131     {
132         final long differenceMemoryCache = thrashCache();
133         LOG.info( "Memory Difference is: " + differenceMemoryCache );
134         assertTrue( differenceMemoryCache < 500000 );
135 
136         //LOG.info( "Memory Used is: " + measureMemoryUse() );
137     }
138 
139     /**
140      * @return time
141      * @throws Exception
142      */
143     protected long thrashCache()
144         throws Exception
145     {
146         final long startingSize = measureMemoryUse();
147         LOG.info( "Memory Used is: " + startingSize );
148 
149         final String value = "value";
150         final String key = "key";
151 
152         // Add the entry
153         jcs.put( key, value );
154 
155         // Create 15 threads that read the keys;
156         final List<Executable> executables = new ArrayList<>();
157         for ( int i = 0; i < 15; i++ )
158         {
159             final JCSThrashTest.Executable executable = () ->
160             {
161                 for ( int j = 0; j < 500; j++ )
162                 {
163                     final String keyj = "key" + j;
164                     jcs.get( keyj );
165                 }
166                 jcs.get( "key" );
167             };
168             executables.add( executable );
169         }
170 
171         // Create 15 threads that are insert 500 keys with large byte[] as
172         // values
173         for ( int i = 0; i < 15; i++ )
174         {
175             final JCSThrashTest.Executable executable = () ->
176             {
177 
178                 // Add a bunch of entries
179                 for ( int j = 0; j < 500; j++ )
180                 {
181                     // Use a random length value
182                     final String keyj = "key" + j;
183                     final byte[] valuej = new byte[10000];
184                     jcs.put( keyj, valuej );
185                 }
186             };
187             executables.add( executable );
188         }
189 
190         runThreads( executables );
191         jcs.clear();
192 
193         final long finishingSize = measureMemoryUse();
194         LOG.info( "Memory Used is: " + finishingSize );
195         return finishingSize - startingSize;
196     }
197 
198     /**
199      * Runs a set of threads, for a fixed amount of time.
200      * <p>
201      * @param executables
202      * @throws Exception
203      */
204     protected void runThreads( final List<Executable> executables )
205         throws Exception
206     {
207 
208         final long endTime = System.currentTimeMillis() + 10000;
209         final Throwable[] errors = new Throwable[1];
210 
211         // Spin up the threads
212         final Thread[] threads = new Thread[executables.size()];
213         for ( int i = 0; i < threads.length; i++ )
214         {
215             final JCSThrashTest.Executable executable = executables.get( i );
216             threads[i] = new Thread()
217             {
218                 @Override
219                 public void run()
220                 {
221                     try
222                     {
223                         // Run the thread until the given end time
224                         while ( System.currentTimeMillis() < endTime )
225                         {
226                             executable.execute();
227                         }
228                     }
229                     catch ( final Throwable t )
230                     {
231                         // Hang on to any errors
232                         errors[0] = t;
233                     }
234                 }
235             };
236             threads[i].start();
237         }
238 
239         // Wait for the threads to finish
240         for (final Thread thread : threads) {
241             thread.join();
242         }
243 
244         // Throw any error that happened
245         if ( errors[0] != null )
246         {
247             throw new Exception( "Test thread failed.", errors[0] );
248         }
249     }
250 
251     /**
252      * Measure memory used by the VM.
253      * <p>
254      * @return bytes
255      * @throws InterruptedException
256      */
257     protected long measureMemoryUse()
258         throws InterruptedException
259     {
260         System.gc();
261         Thread.sleep( 3000 );
262         System.gc();
263         return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
264     }
265 
266     /**
267      * A runnable, that can throw an exception.
268      */
269     protected interface Executable
270     {
271         /**
272          * Executes this object.
273          * @throws Exception
274          */
275         void execute()
276             throws Exception;
277     }
278 
279     /**
280      * @return size
281      */
282     private int getListSize()
283     {
284         final String listSize = "List Size";
285         final String lruMemoryCache = "LRU Memory Cache";
286         String result = "0";
287         final List<IStats> istats = jcs.getStatistics().getAuxiliaryCacheStats();
288         for ( final IStats istat : istats )
289         {
290             final List<IStatElement<?>> statElements = istat.getStatElements();
291             if ( lruMemoryCache.equals( istat.getTypeName() ) )
292             {
293                 for ( final IStatElement<?> statElement : statElements )
294                 {
295                     if ( listSize.equals( statElement.getName() ) )
296                     {
297                         result = statElement.getData().toString();
298                         break;
299                     }
300                 }
301             }
302         }
303         return Integer.parseInt( result );
304     }
305 }