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