1 package org.apache.commons.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.util.Set;
23
24 import org.apache.commons.jcs.engine.behavior.ICacheElement;
25 import org.apache.commons.jcs.engine.behavior.IElementAttributes;
26 import org.apache.commons.jcs.engine.control.CompositeCache;
27 import org.apache.commons.jcs.engine.control.event.behavior.ElementEventType;
28 import org.apache.commons.jcs.engine.memory.behavior.IMemoryCache;
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31
32 /**
33 * A background memory shrinker. Memory problems and concurrent modification exception caused by
34 * acting directly on an iterator of the underlying memory cache should have been solved.
35 * @version $Id: ShrinkerThread.java 1719092 2015-12-10 15:07:30Z tv $
36 */
37 public class ShrinkerThread<K, V>
38 implements Runnable
39 {
40 /** The logger */
41 private static final Log log = LogFactory.getLog( ShrinkerThread.class );
42
43 /** The CompositeCache instance which this shrinker is watching */
44 private final CompositeCache<K, V> cache;
45
46 /** Maximum memory idle time for the whole cache */
47 private final long maxMemoryIdleTime;
48
49 /** Maximum number of items to spool per run. Default is -1, or no limit. */
50 private final int maxSpoolPerRun;
51
52 /** Should we limit the number spooled per run. If so, the maxSpoolPerRun will be used. */
53 private boolean spoolLimit = false;
54
55 /**
56 * Constructor for the ShrinkerThread object.
57 * <p>
58 * @param cache The MemoryCache which the new shrinker should watch.
59 */
60 public ShrinkerThread( CompositeCache<K, V> cache )
61 {
62 super();
63
64 this.cache = cache;
65
66 long maxMemoryIdleTimeSeconds = cache.getCacheAttributes().getMaxMemoryIdleTimeSeconds();
67
68 if ( maxMemoryIdleTimeSeconds < 0 )
69 {
70 this.maxMemoryIdleTime = -1;
71 }
72 else
73 {
74 this.maxMemoryIdleTime = maxMemoryIdleTimeSeconds * 1000;
75 }
76
77 this.maxSpoolPerRun = cache.getCacheAttributes().getMaxSpoolPerRun();
78 if ( this.maxSpoolPerRun != -1 )
79 {
80 this.spoolLimit = true;
81 }
82
83 }
84
85 /**
86 * Main processing method for the ShrinkerThread object
87 */
88 @Override
89 public void run()
90 {
91 shrink();
92 }
93
94 /**
95 * This method is called when the thread wakes up. First the method obtains an array of keys for
96 * the cache region. It iterates through the keys and tries to get the item from the cache
97 * without affecting the last access or position of the item. The item is checked for
98 * expiration, the expiration check has 3 parts:
99 * <ol>
100 * <li>Has the cacheattributes.MaxMemoryIdleTimeSeconds defined for the region been exceeded? If
101 * so, the item should be move to disk.</li> <li>Has the item exceeded MaxLifeSeconds defined in
102 * the element attributes? If so, remove it.</li> <li>Has the item exceeded IdleTime defined in
103 * the element attributes? If so, remove it. If there are event listeners registered for the
104 * cache element, they will be called.</li>
105 * </ol>
106 * TODO Change element event handling to use the queue, then move the queue to the region and
107 * access via the Cache.
108 */
109 protected void shrink()
110 {
111 if ( log.isDebugEnabled() )
112 {
113 log.debug( "Shrinking memory cache for: " + this.cache.getCacheName() );
114 }
115
116 IMemoryCache<K, V> memCache = cache.getMemoryCache();
117
118 try
119 {
120 Set<K> keys = memCache.getKeySet();
121 int size = keys.size();
122 if ( log.isDebugEnabled() )
123 {
124 log.debug( "Keys size: " + size );
125 }
126
127 ICacheElement<K, V> cacheElement;
128 IElementAttributes attributes;
129
130 int spoolCount = 0;
131
132 for (K key : keys)
133 {
134 cacheElement = memCache.getQuiet( key );
135
136 if ( cacheElement == null )
137 {
138 continue;
139 }
140
141 attributes = cacheElement.getElementAttributes();
142
143 boolean remove = false;
144
145 long now = System.currentTimeMillis();
146
147 // If the element is not eternal, check if it should be
148 // removed and remove it if so.
149 if ( !cacheElement.getElementAttributes().getIsEternal() )
150 {
151 remove = cache.isExpired( cacheElement, now,
152 ElementEventType.EXCEEDED_MAXLIFE_BACKGROUND,
153 ElementEventType.EXCEEDED_IDLETIME_BACKGROUND );
154
155 if ( remove )
156 {
157 memCache.remove( cacheElement.getKey() );
158 }
159 }
160
161 // If the item is not removed, check is it has been idle
162 // long enough to be spooled.
163
164 if ( !remove && maxMemoryIdleTime != -1 )
165 {
166 if ( !spoolLimit || spoolCount < this.maxSpoolPerRun )
167 {
168 final long lastAccessTime = attributes.getLastAccessTime();
169
170 if ( lastAccessTime + maxMemoryIdleTime < now )
171 {
172 if ( log.isDebugEnabled() )
173 {
174 log.debug( "Exceeded memory idle time: " + cacheElement.getKey() );
175 }
176
177 // Shouldn't we ensure that the element is
178 // spooled before removing it from memory?
179 // No the disk caches have a purgatory. If it fails
180 // to spool that does not affect the
181 // responsibilities of the memory cache.
182
183 spoolCount++;
184
185 memCache.remove( cacheElement.getKey() );
186
187 memCache.waterfal( cacheElement );
188
189 key = null;
190 cacheElement = null;
191 }
192 }
193 else
194 {
195 if ( log.isDebugEnabled() )
196 {
197 log.debug( "spoolCount = '" + spoolCount + "'; " + "maxSpoolPerRun = '" + maxSpoolPerRun
198 + "'" );
199 }
200
201 // stop processing if limit has been reached.
202 if ( spoolLimit && spoolCount >= this.maxSpoolPerRun )
203 {
204 return;
205 }
206 }
207 }
208 }
209 }
210 catch ( Throwable t )
211 {
212 log.info( "Unexpected trouble in shrink cycle", t );
213
214 // concurrent modifications should no longer be a problem
215 // It is up to the IMemoryCache to return an array of keys
216
217 // stop for now
218 return;
219 }
220 }
221 }