001package org.apache.commons.jcs3.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.jcs3.engine.behavior.ICacheElement; 025import org.apache.commons.jcs3.engine.behavior.IElementAttributes; 026import org.apache.commons.jcs3.engine.control.CompositeCache; 027import org.apache.commons.jcs3.engine.control.event.behavior.ElementEventType; 028import org.apache.commons.jcs3.engine.memory.behavior.IMemoryCache; 029import org.apache.commons.jcs3.log.Log; 030import org.apache.commons.jcs3.log.LogManager; 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 */ 036public class ShrinkerThread<K, V> 037 implements Runnable 038{ 039 /** The logger */ 040 private static final Log log = LogManager.getLog( ShrinkerThread.class ); 041 042 /** The CompositeCache instance which this shrinker is watching */ 043 private final CompositeCache<K, V> cache; 044 045 /** Maximum memory idle time for the whole cache */ 046 private final long maxMemoryIdleTime; 047 048 /** Maximum number of items to spool per run. Default is -1, or no limit. */ 049 private final int maxSpoolPerRun; 050 051 /** Should we limit the number spooled per run. If so, the maxSpoolPerRun will be used. */ 052 private boolean spoolLimit; 053 054 /** 055 * Constructor for the ShrinkerThread object. 056 * <p> 057 * @param cache The MemoryCache which the new shrinker should watch. 058 */ 059 public ShrinkerThread( final CompositeCache<K, V> cache ) 060 { 061 this.cache = cache; 062 063 final long maxMemoryIdleTimeSeconds = cache.getCacheAttributes().getMaxMemoryIdleTimeSeconds(); 064 065 if ( maxMemoryIdleTimeSeconds < 0 ) 066 { 067 this.maxMemoryIdleTime = -1; 068 } 069 else 070 { 071 this.maxMemoryIdleTime = maxMemoryIdleTimeSeconds * 1000; 072 } 073 074 this.maxSpoolPerRun = cache.getCacheAttributes().getMaxSpoolPerRun(); 075 if ( this.maxSpoolPerRun != -1 ) 076 { 077 this.spoolLimit = true; 078 } 079 080 } 081 082 /** 083 * Main processing method for the ShrinkerThread object 084 */ 085 @Override 086 public void run() 087 { 088 shrink(); 089 } 090 091 /** 092 * This method is called when the thread wakes up. First the method obtains an array of keys for 093 * the cache region. It iterates through the keys and tries to get the item from the cache 094 * without affecting the last access or position of the item. The item is checked for 095 * expiration, the expiration check has 3 parts: 096 * <ol> 097 * <li>Has the cacheattributes.MaxMemoryIdleTimeSeconds defined for the region been exceeded? If 098 * so, the item should be move to disk.</li> <li>Has the item exceeded MaxLifeSeconds defined in 099 * 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}