View Javadoc
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 }