Coverage report

  %line %branch
org.apache.jcs.engine.memory.shrinking.ShrinkerThread
64% 
77% 

 1  
 package org.apache.jcs.engine.memory.shrinking;
 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.IOException;
 23  
 import java.io.Serializable;
 24  
 import java.util.ArrayList;
 25  
 import java.util.Iterator;
 26  
 
 27  
 import org.apache.commons.logging.Log;
 28  
 import org.apache.commons.logging.LogFactory;
 29  
 import org.apache.jcs.engine.behavior.ICacheElement;
 30  
 import org.apache.jcs.engine.behavior.IElementAttributes;
 31  
 import org.apache.jcs.engine.control.event.ElementEvent;
 32  
 import org.apache.jcs.engine.control.event.behavior.IElementEvent;
 33  
 import org.apache.jcs.engine.control.event.behavior.IElementEventConstants;
 34  
 import org.apache.jcs.engine.control.event.behavior.IElementEventHandler;
 35  
 import org.apache.jcs.engine.memory.MemoryCache;
 36  
 
 37  
 /**
 38  
  * A background memory shrinker. Memory problems and concurrent modification
 39  
  * exception caused by acting directly on an iterator of the underlying memory
 40  
  * cache should have been solved.
 41  
  *
 42  
  * @version $Id: ShrinkerThread.java 536904 2007-05-10 16:03:42Z tv $
 43  
  */
 44  3
 public class ShrinkerThread
 45  
     implements Runnable
 46  
 {
 47  45
     private final static Log log = LogFactory.getLog( ShrinkerThread.class );
 48  
 
 49  
     /** The MemoryCache instance which this shrinker is watching */
 50  
     private final MemoryCache cache;
 51  
 
 52  
     /** Maximum memory idle time for the whole cache */
 53  
     private final long maxMemoryIdleTime;
 54  
 
 55  
     /** Maximum number of items to spool per run. Default is -1, or no limit. */
 56  
     private int maxSpoolPerRun;
 57  
 
 58  72
     private boolean spoolLimit = false;
 59  
 
 60  
     /**
 61  
      * Constructor for the ShrinkerThread object.
 62  
      *
 63  
      * @param cache
 64  
      *            The MemoryCache which the new shrinker should watch.
 65  
      */
 66  
     public ShrinkerThread( MemoryCache cache )
 67  
     {
 68  72
         super();
 69  
 
 70  72
         this.cache = cache;
 71  
 
 72  72
         long maxMemoryIdleTimeSeconds = cache.getCacheAttributes().getMaxMemoryIdleTimeSeconds();
 73  
 
 74  72
         if ( maxMemoryIdleTimeSeconds < 0 )
 75  
         {
 76  0
             this.maxMemoryIdleTime = -1;
 77  0
         }
 78  
         else
 79  
         {
 80  72
             this.maxMemoryIdleTime = maxMemoryIdleTimeSeconds * 1000;
 81  
         }
 82  
 
 83  72
         this.maxSpoolPerRun = cache.getCacheAttributes().getMaxSpoolPerRun();
 84  72
         if ( this.maxSpoolPerRun != -1 )
 85  
         {
 86  24
             this.spoolLimit = true;
 87  
         }
 88  
 
 89  72
     }
 90  
 
 91  
     /**
 92  
      * Main processing method for the ShrinkerThread object
 93  
      */
 94  
     public void run()
 95  
     {
 96  156
         shrink();
 97  155
     }
 98  
 
 99  
     /**
 100  
      * This method is called when the thread wakes up. Frist the method obtains
 101  
      * an array of keys for the cache region. It iterates through the keys and
 102  
      * tries to get the item from the cache without affecting the last access or
 103  
      * position of the item. The item is checked for expiration, the expiration
 104  
      * check has 3 parts:
 105  
      * <ol>
 106  
      * <li>Has the cacheattributes.MaxMemoryIdleTimeSeconds defined for the
 107  
      * region been exceeded? If so, the item should be move to disk.</li>
 108  
      * <li>Has the item exceeded MaxLifeSeconds defined in the element
 109  
      * attributes? If so, remove it.</li>
 110  
      * <li>Has the item exceeded IdleTime defined in the element atributes? If
 111  
      * so, remove it. If there are event listeners registered for the cache
 112  
      * element, they will be called.</li>
 113  
      * </ol>
 114  
      *
 115  
      * @todo Change element event handling to use the queue, then move the queue
 116  
      *       to the region and access via the Cache.
 117  
      */
 118  
     protected void shrink()
 119  
     {
 120  156
         if ( log.isDebugEnabled() )
 121  
         {
 122  0
             if ( this.cache.getCompositeCache() != null )
 123  
             {
 124  0
                 log.debug( "Shrinking memory cache for: " + this.cache.getCompositeCache().getCacheName() );
 125  
             }
 126  
         }
 127  
 
 128  
         try
 129  
         {
 130  156
             Object[] keys = cache.getKeyArray();
 131  156
             int size = keys.length;
 132  156
             if ( log.isDebugEnabled() )
 133  
             {
 134  0
                 log.debug( "Keys size: " + size );
 135  
             }
 136  
 
 137  
             Serializable key;
 138  
             ICacheElement cacheElement;
 139  
             IElementAttributes attributes;
 140  
 
 141  156
             int spoolCount = 0;
 142  
 
 143  17008
             for ( int i = 0; i < size; i++ )
 144  
             {
 145  16870
                 key = (Serializable) keys[i];
 146  16870
                 cacheElement = cache.getQuiet( key );
 147  
 
 148  16871
                 if ( cacheElement == null )
 149  
                 {
 150  2145
                     continue;
 151  
                 }
 152  
 
 153  14727
                 attributes = cacheElement.getElementAttributes();
 154  
 
 155  14727
                 boolean remove = false;
 156  
 
 157  14728
                 long now = System.currentTimeMillis();
 158  
 
 159  
                 // Useful, but overkill even for DEBUG since it is written for
 160  
                 // every element in memory
 161  
                 //
 162  
                 // if ( log.isDebugEnabled() )
 163  
                 // {
 164  
                 // log.debug( "IsEternal: " + attributes.getIsEternal() );
 165  
                 // log.debug( "MaxLifeSeconds: "
 166  
                 // + attributes.getMaxLifeSeconds() );
 167  
                 // log.debug( "CreateTime:" + attributes.getCreateTime() );
 168  
                 // }
 169  
 
 170  
                 // If the element is not eternal, check if it should be
 171  
                 // removed and remove it if so.
 172  
 
 173  14728
                 if ( !cacheElement.getElementAttributes().getIsEternal() )
 174  
                 {
 175  14729
                     remove = checkForRemoval( cacheElement, now );
 176  
 
 177  14729
                     if ( remove )
 178  
                     {
 179  0
                         cache.remove( cacheElement.getKey() );
 180  
                     }
 181  
                 }
 182  
 
 183  
                 // If the item is not removed, check is it has been idle
 184  
                 // long enough to be spooled.
 185  
 
 186  14729
                 if ( !remove && ( maxMemoryIdleTime != -1 ) )
 187  
                 {
 188  14730
                     if ( !spoolLimit || ( spoolCount < this.maxSpoolPerRun ) )
 189  
                     {
 190  
 
 191  14714
                         final long lastAccessTime = attributes.getLastAccessTime();
 192  
 
 193  14714
                         if ( lastAccessTime + maxMemoryIdleTime < now )
 194  
                         {
 195  56
                             if ( log.isDebugEnabled() )
 196  
                             {
 197  0
                                 log.debug( "Exceeded memory idle time: " + cacheElement.getKey() );
 198  
                             }
 199  
 
 200  
                             // Shouldn't we ensure that the element is
 201  
                             // spooled before removing it from memory?
 202  
                             // No the disk caches have a purgatory. If it fails
 203  
                             // to spool that does not affect the
 204  
                             // responsibilities of the memory cache.
 205  
 
 206  56
                             spoolCount++;
 207  
 
 208  56
                             cache.remove( cacheElement.getKey() );
 209  
 
 210  56
                             cache.waterfal( cacheElement );
 211  
 
 212  56
                             key = null;
 213  56
                             cacheElement = null;
 214  
                         }
 215  13891
                     }
 216  
                     else
 217  
                     {
 218  16
                         if ( log.isDebugEnabled() )
 219  
                         {
 220  0
                             log.debug( "spoolCount = '" + spoolCount + "'; " + "maxSpoolPerRun = '" + maxSpoolPerRun
 221  
                                 + "'" );
 222  
                         }
 223  
 
 224  
                         // stop processing if limit has been reached.
 225  16
                         if ( spoolLimit && ( spoolCount >= this.maxSpoolPerRun ) )
 226  
                         {
 227  16
                             keys = null;
 228  16
                             return;
 229  
                         }
 230  
                     }
 231  
                 }
 232  
             }
 233  
 
 234  139
             keys = null;
 235  
         }
 236  0
         catch ( Throwable t )
 237  
         {
 238  0
             log.info( "Unexpected trouble in shrink cycle", t );
 239  
 
 240  
             // concurrent modifications should no longer be a problem
 241  
             // It is up to the IMemoryCache to return an array of keys
 242  
 
 243  
             // stop for now
 244  0
             return;
 245  127
         }
 246  
 
 247  139
     }
 248  
 
 249  
     /**
 250  
      * Check if either lifetime or idletime has expired for the provided event,
 251  
      * and remove it from the cache if so.
 252  
      *
 253  
      * @param cacheElement
 254  
      *            Element to check for expiration
 255  
      * @param now
 256  
      *            Time to consider expirations relative to
 257  
      * @return true if the element should be removed, or false.
 258  
      * @throws IOException
 259  
      */
 260  
     private boolean checkForRemoval( ICacheElement cacheElement, long now )
 261  
         throws IOException
 262  
     {
 263  14739
         IElementAttributes attributes = cacheElement.getElementAttributes();
 264  
 
 265  14739
         final long maxLifeSeconds = attributes.getMaxLifeSeconds();
 266  14740
         final long createTime = attributes.getCreateTime();
 267  
 
 268  
         // Check if maxLifeSeconds has been exceeded
 269  14740
         if ( maxLclass="keyword">ifeSeconds != -1 && now - createTime > maxLclass="keyword">ifeSeconds * 1000 )
 270  
         {
 271  0
             if ( log.isInfoEnabled() )
 272  
             {
 273  0
                 log.info( "Exceeded maxLifeSeconds: " + cacheElement.getKey() );
 274  
             }
 275  
 
 276  0
             handleElementEvents( cacheElement, IElementEventConstants.ELEMENT_EVENT_EXCEEDED_MAXLIFE_BACKGROUND );
 277  
 
 278  0
             return true;
 279  
         }
 280  
 
 281  14741
         final long idleTime = attributes.getIdleTime();
 282  14741
         final long lastAccessTime = attributes.getLastAccessTime();
 283  
 
 284  
         // Check maxIdleTime has been exceeded
 285  14742
         if ( idleTime != -1 && now - lastAccessTime > idleTime * 1000 )
 286  
         {
 287  0
             if ( log.isInfoEnabled() )
 288  
             {
 289  0
                 log.info( "Exceeded maxIdleTime " + cacheElement.getKey() );
 290  
             }
 291  
 
 292  0
             handleElementEvents( cacheElement, IElementEventConstants.ELEMENT_EVENT_EXCEEDED_IDLETIME_BACKGROUND );
 293  
 
 294  0
             return true;
 295  
         }
 296  
 
 297  14742
         return false;
 298  
     }
 299  
 
 300  
     /**
 301  
      * Handle any events registered for the given element of the given event
 302  
      * type.
 303  
      *
 304  
      * @param cacheElement
 305  
      *            Element to handle events for
 306  
      * @param eventType
 307  
      *            Type of event to handle
 308  
      * @throws IOException
 309  
      *             If an error occurs
 310  
      */
 311  
     private void handleElementEvents( ICacheElement cacheElement, int eventType )
 312  
         throws IOException
 313  
     {
 314  0
         IElementAttributes attributes = cacheElement.getElementAttributes();
 315  
 
 316  0
         ArrayList eventHandlers = attributes.getElementEventHandlers();
 317  
 
 318  0
         if ( eventHandlers != null )
 319  
         {
 320  0
             if ( log.isDebugEnabled() )
 321  
             {
 322  0
                 log.debug( "Handlers are registered, type: " + eventType );
 323  
             }
 324  
 
 325  0
             IElementEvent event = new ElementEvent( cacheElement, eventType );
 326  
 
 327  0
             Iterator handlerIter = eventHandlers.iterator();
 328  
 
 329  0
             while ( handlerIter.hasNext() )
 330  
             {
 331  0
                 IElementEventHandler hand = (IElementEventHandler) handlerIter.next();
 332  
 
 333  
                 // extra safety
 334  
                 // TODO we shouldn't be operating on a variable of another class.
 335  
                 // we did this to get away from the singleton composite cache.
 336  
                 // we will need to create an event manager and pass it around instead.
 337  0
                 if ( cache.getCompositeCache() != null )
 338  
                 {
 339  0
                     cache.getCompositeCache().addElementEvent( hand, event );
 340  
                 }
 341  0
             }
 342  
         }
 343  0
     }
 344  
 }

This report is generated by jcoverage, Maven and Maven JCoverage Plugin.