1 package org.apache.jcs.engine.memory.shrinking;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
160
161
162
163
164
165
166
167
168
169
170
171
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
184
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
201
202
203
204
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
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
241
242
243
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
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
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
334
335
336
337 if ( cache.getCompositeCache() != null )
338 {
339 cache.getCompositeCache().addElementEvent( hand, event );
340 }
341 }
342 }
343 }
344 }