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