/* * Licensed to the Apache Software Foundation (ASF) under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional information regarding * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. You may obtain a * copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package org.apache.geode.internal.cache.lru; import org.apache.geode.*; import org.apache.geode.cache.*; import org.apache.geode.internal.statistics.StatisticsTypeFactoryImpl; import org.apache.geode.internal.cache.*; import org.apache.geode.internal.i18n.LocalizedStrings; import java.util.*; /** * A <code>CapacityController</code> that will remove the least recently used (LRU) entry from a * region once the region reaches a certain capacity. The entry is locally destroyed when evicted by * the capacity controller. * <P> * This is not supported as the capacity controller of a region with mirroring enabled. * * <P> * LRUCapacityController must be set in the RegionAttributes before the region is created. A Region * with LRUCapacityController set will throw an IllegalStateException if an attempt is made to * replace the Region's capacity controller. While the capacity controller cannot be replaced, it * does support changing the limit with the setMaximumEntries method. * * <P> * If you are using a <code>cache.xml</code> file to create a JCache region declaratively, you can * include the following to associate a <code>LRUCapacityController</code> with a region: * * <pre> * <region-attributes> * <capacity-controller> * <classname>org.apache.geode.cache.LRUCapacityController</classname> * <parameter name="maximum-entries">1000</parameter> * </capacity-controller> * </region-attributes> * </pre> * * @since GemFire 2.0.2 */ public final class LRUCapacityController extends LRUAlgorithm implements Declarable { private static final long serialVersionUID = -4383074909189355938L; /** * The default maximum number of entries allowed by an LRU capacity controller is 900. */ public static final int DEFAULT_MAXIMUM_ENTRIES = EvictionAttributes.DEFAULT_ENTRIES_MAXIMUM; /** * The key for setting the maximum-entries property declaratively. * * @see #init */ public static final String MAXIMUM_ENTRIES = "maximum-entries"; protected static final StatisticsType statType; static { StatisticsTypeFactory f = StatisticsTypeFactoryImpl.singleton(); final String entriesAllowedDesc = "Number of entries allowed in this region."; final String regionEntryCountDesc = "Number of entries in this region."; final String lruEvictionsDesc = "Number of total entry evictions triggered by LRU."; final String lruDestroysDesc = "Number of entries destroyed in the region through both destroy cache operations and eviction. Reset to zero each time it exceeds lruDestroysLimit."; final String lruDestroysLimitDesc = "Maximum number of entry destroys triggered by LRU before scan occurs."; final String lruEvaluationsDesc = "Number of entries evaluated during LRU operations."; final String lruGreedyReturnsDesc = "Number of non-LRU entries evicted during LRU operations"; statType = f.createType("LRUStatistics", "Statistics about entry based Least Recently Used region entry disposal", new StatisticDescriptor[] { f.createLongGauge("entriesAllowed", entriesAllowedDesc, "entries"), f.createLongGauge("entryCount", regionEntryCountDesc, "entries"), f.createLongCounter("lruEvictions", lruEvictionsDesc, "entries"), f.createLongCounter("lruDestroys", lruDestroysDesc, "entries"), f.createLongGauge("lruDestroysLimit", lruDestroysLimitDesc, "entries"), f.createLongCounter("lruEvaluations", lruEvaluationsDesc, "entries"), f.createLongCounter("lruGreedyReturns", lruGreedyReturnsDesc, "entries"),}); } ////////////////////// Instance Fields ///////////////////// /** The maximum number entries allowed by this controller */ private volatile int maximumEntries; /////////////////////// Constructors /////////////////////// /** * Creates an LRU capacity controller that allows the {@link #DEFAULT_MAXIMUM_ENTRIES default} * maximum number of entries and the * {@link org.apache.geode.cache.EvictionAction#DEFAULT_EVICTION_ACTION default} eviction action. * * @see #LRUCapacityController(int,Region) */ public LRUCapacityController(Region region) { this(DEFAULT_MAXIMUM_ENTRIES, EvictionAction.DEFAULT_EVICTION_ACTION, region); } /** * Creates an LRU capacity controller that allows the given number of maximum entries and uses the * default eviction action. * * @param maximumEntries The maximum number of entries allowed in the region whose capacity this * controller controls. Once there are <code>capacity</code> entries in a region, this * controller will remove the least recently used entry.<br> * <p> * For a region with {@link DataPolicy#PARTITION}, the maximum number of entries allowed in * the region, collectively for its primary buckets and redundant copies for this VM. After * there are <code>capacity</code> entries in the region's primary buckets and redundant * copies for this VM, this controller will remove the least recently used entry from the * bucket in which the subsequent <code>put</code> takes place. */ public LRUCapacityController(int maximumEntries, Region region) { this(maximumEntries, EvictionAction.DEFAULT_EVICTION_ACTION, region); } /** * Creates an LRU capacity controller that allows the given number of maximum entries. * * @param maximumEntries The maximum number of entries allowed in the region whose capacity this * controller controls. Once there are <code>capacity</code> entries in a region, this * controller will remove the least recently used entry.<br> * <p> * For a region with {@link DataPolicy#PARTITION}, the maximum number of entries allowed in * the region, collectively for its primary buckets and redundant copies for this VM. After * there are <code>capacity</code> entries in the region's primary buckets and redundant * copies for this VM, this controller will remove the least recently used entry from the * bucket in which the subsequent <code>put</code> takes place. * @param evictionAction The action to perform upon the least recently used entry. See * {@link #EVICTION_ACTION}. */ public LRUCapacityController(int maximumEntries, EvictionAction evictionAction, Region region) { super(evictionAction, region); setMaximumEntries(maximumEntries); } /** * Sets the limit on the number of entries allowed. This change takes place on next region * operation that could increase the region size. */ public void setMaximumEntries(int maximumEntries) { if (maximumEntries <= 0) throw new IllegalArgumentException( LocalizedStrings.LRUCapacityController_MAXIMUM_ENTRIES_MUST_BE_POSITIVE .toLocalizedString()); this.maximumEntries = maximumEntries; if (bucketRegion != null) { bucketRegion.setLimit(this.maximumEntries); } else if (this.stats != null) { this.stats.setLimit(this.maximumEntries); } } @Override public void setLimit(int max) { setMaximumEntries(max); } ////////////////////// Instance Methods ///////////////////// /** * Because an <code>LRUCapacityController</code> is {@link Declarable}, it can be initialized with * properties. The {@link #MAXIMUM_ENTRIES "maximum-entries"} (case-sensitive) property can be * used to specify the capacity allowed by this controller. Other properties in props are ignored. * The {@link #EVICTION_ACTION "eviction-action"} property specifies the action to be taken when * the region has reached its capacity. * * @throws NumberFormatException The <code>maximum-entries</code> property cannot be parsed as an * integer * @throws IllegalArgumentException The value of the <code>eviction-action</code> property is not * recoginzed. */ public void init(Properties props) throws NumberFormatException { String prop = null; if ((prop = props.getProperty(MAXIMUM_ENTRIES)) != null) { this.maximumEntries = Integer.parseInt(prop); } if ((prop = props.getProperty(EVICTION_ACTION)) != null) { setEvictionAction(EvictionAction.parseAction(prop)); } } // Candidate for removal since capacity controller no longer part of // cache.xml @Override public Properties getProperties() { Properties props = new Properties(); if (this.evictionAction != EvictionAction.DEFAULT_EVICTION_ACTION) { props.setProperty(EVICTION_ACTION, this.evictionAction.toString()); } if (this.maximumEntries != DEFAULT_MAXIMUM_ENTRIES) { props.setProperty(MAXIMUM_ENTRIES, String.valueOf(this.maximumEntries)); } return props; } @Override public long getLimit() { return this.maximumEntries; } @Override protected EnableLRU createLRUHelper() { return new AbstractEnableLRU() { /** * Indicate what kind of <code>EvictionAlgorithm</code> this helper implements */ public EvictionAlgorithm getEvictionAlgorithm() { return EvictionAlgorithm.LRU_ENTRY; } /** * All entries for the LRUCapacityController are considered to be of size 1. */ public int entrySize(Object key, Object value) throws IllegalArgumentException { if (Token.isRemoved(value) /* && (value != Token.TOMBSTONE) */) { // un-comment to make // tombstones visible // bug #42228 - lruEntryDestroy removes an entry from the LRU, but if // it is subsequently resurrected we want the new entry to generate a delta return 0; } if ((value == null /* overflow to disk */ || value == Token.INVALID || value == Token.LOCAL_INVALID) && getEvictionAction().isOverflowToDisk()) { // Don't count this guys toward LRU return 0; } else { return 1; } } public StatisticsType getStatisticsType() { return statType; } public String getStatisticsName() { return "LRUStatistics"; } public int getLimitStatId() { return statType.nameToId("entriesAllowed"); } public int getCountStatId() { return statType.nameToId("entryCount"); } public int getEvictionsStatId() { return statType.nameToId("lruEvictions"); } public int getDestroysStatId() { return statType.nameToId("lruDestroys"); } public int getDestroysLimitStatId() { return statType.nameToId("lruDestroysLimit"); } public int getEvaluationsStatId() { return statType.nameToId("lruEvaluations"); } public int getGreedyReturnsStatId() { return statType.nameToId("lruGreedyReturns"); } public boolean mustEvict(LRUStatistics stats, Region region, int delta) { return stats.getCounter() + delta > stats.getLimit(); } }; } @Override public boolean equals(Object cc) { if (!super.equals(cc)) return false; if (!(cc instanceof LRUCapacityController)) return false; LRUCapacityController other = (LRUCapacityController) cc; if (this.maximumEntries != other.maximumEntries) return false; return true; } /* * (non-Javadoc) * * @see java.lang.Object#hashCode() * * Note that we just need to make sure that equal objects return equal hashcodes; nothing really * elaborate is done here. */ @Override public int hashCode() { int result = super.hashCode(); result += this.maximumEntries; return result; } /** * Returns a brief description of this capacity controller. * * @since GemFire 4.0 */ @Override public String toString() { return LocalizedStrings.LRUCapacityController_LRUCAPACITYCONTROLLER_WITH_A_CAPACITY_OF_0_ENTRIES_AND_EVICTION_ACTION_1 .toLocalizedString(new Object[] {Long.valueOf(this.getLimit()), this.getEvictionAction()}); } }