View Javadoc

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  public class ShrinkerThread
45      implements Runnable
46  {
47      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      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          super();
69  
70          this.cache = cache;
71  
72          long maxMemoryIdleTimeSeconds = cache.getCacheAttributes().getMaxMemoryIdleTimeSeconds();
73  
74          if ( maxMemoryIdleTimeSeconds < 0 )
75          {
76              this.maxMemoryIdleTime = -1;
77          }
78          else
79          {
80              this.maxMemoryIdleTime = maxMemoryIdleTimeSeconds * 1000;
81          }
82  
83          this.maxSpoolPerRun = cache.getCacheAttributes().getMaxSpoolPerRun();
84          if ( this.maxSpoolPerRun != -1 )
85          {
86              this.spoolLimit = true;
87          }
88  
89      }
90  
91      /***
92       * Main processing method for the ShrinkerThread object
93       */
94      public void run()
95      {
96          shrink();
97      }
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         if ( log.isDebugEnabled() )
121         {
122             if ( this.cache.getCompositeCache() != null )
123             {
124                 log.debug( "Shrinking memory cache for: " + this.cache.getCompositeCache().getCacheName() );
125             }
126         }
127 
128         try
129         {
130             Object[] keys = cache.getKeyArray();
131             int size = keys.length;
132             if ( log.isDebugEnabled() )
133             {
134                 log.debug( "Keys size: " + size );
135             }
136 
137             Serializable key;
138             ICacheElement cacheElement;
139             IElementAttributes attributes;
140 
141             int spoolCount = 0;
142 
143             for ( int i = 0; i < size; i++ )
144             {
145                 key = (Serializable) keys[i];
146                 cacheElement = cache.getQuiet( key );
147 
148                 if ( cacheElement == null )
149                 {
150                     continue;
151                 }
152 
153                 attributes = cacheElement.getElementAttributes();
154 
155                 boolean remove = false;
156 
157                 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                 if ( !cacheElement.getElementAttributes().getIsEternal() )
174                 {
175                     remove = checkForRemoval( cacheElement, now );
176 
177                     if ( remove )
178                     {
179                         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                 if ( !remove && ( maxMemoryIdleTime != -1 ) )
187                 {
188                     if ( !spoolLimit || ( spoolCount < this.maxSpoolPerRun ) )
189                     {
190 
191                         final long lastAccessTime = attributes.getLastAccessTime();
192 
193                         if ( lastAccessTime + maxMemoryIdleTime < now )
194                         {
195                             if ( log.isDebugEnabled() )
196                             {
197                                 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                             spoolCount++;
207 
208                             cache.remove( cacheElement.getKey() );
209 
210                             cache.waterfal( cacheElement );
211 
212                             key = null;
213                             cacheElement = null;
214                         }
215                     }
216                     else
217                     {
218                         if ( log.isDebugEnabled() )
219                         {
220                             log.debug( "spoolCount = '" + spoolCount + "'; " + "maxSpoolPerRun = '" + maxSpoolPerRun
221                                 + "'" );
222                         }
223 
224                         // stop processing if limit has been reached.
225                         if ( spoolLimit && ( spoolCount >= this.maxSpoolPerRun ) )
226                         {
227                             keys = null;
228                             return;
229                         }
230                     }
231                 }
232             }
233 
234             keys = null;
235         }
236         catch ( Throwable t )
237         {
238             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             return;
245         }
246 
247     }
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         IElementAttributes attributes = cacheElement.getElementAttributes();
264 
265         final long maxLifeSeconds = attributes.getMaxLifeSeconds();
266         final long createTime = attributes.getCreateTime();
267 
268         // Check if maxLifeSeconds has been exceeded
269         if ( maxLifeSeconds != -1 && now - createTime > maxLifeSeconds * 1000 )
270         {
271             if ( log.isInfoEnabled() )
272             {
273                 log.info( "Exceeded maxLifeSeconds: " + cacheElement.getKey() );
274             }
275 
276             handleElementEvents( cacheElement, IElementEventConstants.ELEMENT_EVENT_EXCEEDED_MAXLIFE_BACKGROUND );
277 
278             return true;
279         }
280 
281         final long idleTime = attributes.getIdleTime();
282         final long lastAccessTime = attributes.getLastAccessTime();
283 
284         // Check maxIdleTime has been exceeded
285         if ( idleTime != -1 && now - lastAccessTime > idleTime * 1000 )
286         {
287             if ( log.isInfoEnabled() )
288             {
289                 log.info( "Exceeded maxIdleTime " + cacheElement.getKey() );
290             }
291 
292             handleElementEvents( cacheElement, IElementEventConstants.ELEMENT_EVENT_EXCEEDED_IDLETIME_BACKGROUND );
293 
294             return true;
295         }
296 
297         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         IElementAttributes attributes = cacheElement.getElementAttributes();
315 
316         ArrayList eventHandlers = attributes.getElementEventHandlers();
317 
318         if ( eventHandlers != null )
319         {
320             if ( log.isDebugEnabled() )
321             {
322                 log.debug( "Handlers are registered, type: " + eventType );
323             }
324 
325             IElementEvent event = new ElementEvent( cacheElement, eventType );
326 
327             Iterator handlerIter = eventHandlers.iterator();
328 
329             while ( handlerIter.hasNext() )
330             {
331                 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                 if ( cache.getCompositeCache() != null )
338                 {
339                     cache.getCompositeCache().addElementEvent( hand, event );
340                 }
341             }
342         }
343     }
344 }