001package org.apache.commons.jcs.engine.memory.shrinking;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.util.Set;
023
024import org.apache.commons.jcs.engine.behavior.ICacheElement;
025import org.apache.commons.jcs.engine.behavior.IElementAttributes;
026import org.apache.commons.jcs.engine.control.CompositeCache;
027import org.apache.commons.jcs.engine.control.event.behavior.ElementEventType;
028import org.apache.commons.jcs.engine.memory.behavior.IMemoryCache;
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031
032/**
033 * A background memory shrinker. Memory problems and concurrent modification exception caused by
034 * acting directly on an iterator of the underlying memory cache should have been solved.
035 * @version $Id: ShrinkerThread.java 1719092 2015-12-10 15:07:30Z tv $
036 */
037public class ShrinkerThread<K, V>
038    implements Runnable
039{
040    /** The logger */
041    private static final Log log = LogFactory.getLog( ShrinkerThread.class );
042
043    /** The CompositeCache instance which this shrinker is watching */
044    private final CompositeCache<K, V> cache;
045
046    /** Maximum memory idle time for the whole cache */
047    private final long maxMemoryIdleTime;
048
049    /** Maximum number of items to spool per run. Default is -1, or no limit. */
050    private final int maxSpoolPerRun;
051
052    /** Should we limit the number spooled per run. If so, the maxSpoolPerRun will be used. */
053    private boolean spoolLimit = false;
054
055    /**
056     * Constructor for the ShrinkerThread object.
057     * <p>
058     * @param cache The MemoryCache which the new shrinker should watch.
059     */
060    public ShrinkerThread( CompositeCache<K, V> cache )
061    {
062        super();
063
064        this.cache = cache;
065
066        long maxMemoryIdleTimeSeconds = cache.getCacheAttributes().getMaxMemoryIdleTimeSeconds();
067
068        if ( maxMemoryIdleTimeSeconds < 0 )
069        {
070            this.maxMemoryIdleTime = -1;
071        }
072        else
073        {
074            this.maxMemoryIdleTime = maxMemoryIdleTimeSeconds * 1000;
075        }
076
077        this.maxSpoolPerRun = cache.getCacheAttributes().getMaxSpoolPerRun();
078        if ( this.maxSpoolPerRun != -1 )
079        {
080            this.spoolLimit = true;
081        }
082
083    }
084
085    /**
086     * Main processing method for the ShrinkerThread object
087     */
088    @Override
089    public void run()
090    {
091        shrink();
092    }
093
094    /**
095     * This method is called when the thread wakes up. First the method obtains an array of keys for
096     * the cache region. It iterates through the keys and tries to get the item from the cache
097     * without affecting the last access or position of the item. The item is checked for
098     * expiration, the expiration check has 3 parts:
099     * <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}