/*
* 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.InternalGemFireException;
import org.apache.geode.StatisticsFactory;
import org.apache.geode.cache.*;
import org.apache.geode.internal.cache.BucketRegion;
import org.apache.geode.internal.cache.PlaceHolderDiskRegion;
import org.apache.geode.internal.i18n.LocalizedStrings;
import java.io.*;
import java.util.*;
/**
* Eviction controllers that extend this class evict the least recently used (LRU) entry in the
* region whose capacity they controller. In order to provide an efficient computation of the LRU
* entry, GemFire uses special internal data structures for managing the contents of a region. As a
* result, there are several restrictions that are placed on regions whose capacity is governed by
* an LRU algorithm.
*
* <UL>
*
* <LI>If the capacity of a region is to be controlled by an LRU algorithm, then the region must be
* <b>created</b> with {@link org.apache.geode.cache.EvictionAttributes}
*
* <LI>The eviction controller of a region governed by an LRU algorithm cannot be changed.</LI>
*
* <LI>An LRU algorithm cannot be applied to a region after the region has been created.</LI>
*
* </UL>
*
* LRU algorithms also specify what {@linkplain org.apache.geode.cache.EvictionAction action} should
* be performed upon the least recently used entry when the capacity is reached. Currently, there
* are two supported actions: {@linkplain org.apache.geode.cache.EvictionAction#LOCAL_DESTROY
* locally destroying} the entry (which is the
* {@linkplain org.apache.geode.cache.EvictionAction#DEFAULT_EVICTION_ACTION default}), thus freeing
* up space in the VM, and {@linkplain org.apache.geode.cache.EvictionAction#OVERFLOW_TO_DISK
* overflowing} the value of the entry to disk.
*
* <P>
*
* {@link org.apache.geode.cache.EvictionAttributes Eviction controllers} that use an LRU algorithm
* maintain certain region-dependent state (such as the maximum number of entries allowed in the
* region). As a result, an instance of <code>LRUAlgorithm</code> cannot be shared among multiple
* regions. Attempts to create a region with a LRU-based capacity controller that has already been
* used to create another region will result in an {@link IllegalStateException} being thrown.
*
* @since GemFire 3.2
*/
public abstract class LRUAlgorithm implements CacheCallback, Serializable, Cloneable {
/**
* The key for setting the <code>eviction-action</code> property of an <code>LRUAlgorithm</code>
*/
public static final String EVICTION_ACTION = "eviction-action";
//////////////////////// Instance Fields ///////////////////////
/** What to do upon eviction */
protected EvictionAction evictionAction;
/** Used to dynamically track the changing region limit. */
protected transient LRUStatistics stats;
/** The helper created by this LRUAlgorithm */
private transient EnableLRU helper;
protected BucketRegion bucketRegion;
///////////////////////// Constructors /////////////////////////
/**
* Creates a new <code>LRUAlgorithm</code> with the given {@linkplain EvictionAction eviction
* action}.
*/
protected LRUAlgorithm(EvictionAction evictionAction, Region region) {
bucketRegion = (BucketRegion) (region instanceof BucketRegion ? region : null);
setEvictionAction(evictionAction);
this.helper = createLRUHelper();
}
/////////////////////// Instance Methods ///////////////////////
/**
* Used to hook up a bucketRegion late during disk recover.
*/
public void setBucketRegion(Region r) {
if (r instanceof BucketRegion) {
this.bucketRegion = (BucketRegion) r;
this.bucketRegion.setLimit(getLimit());
}
}
/**
* Sets the action that is performed on the least recently used entry when it is evicted from the
* VM.
*
* @throws IllegalArgumentException If <code>evictionAction</code> specifies an unknown eviction
* action.
*
* @see EvictionAction
*/
protected void setEvictionAction(EvictionAction evictionAction) {
this.evictionAction = evictionAction;
}
/**
* Gets the action that is performed on the least recently used entry when it is evicted from the
* VM.
*
* @return one of the following constants: {@link EvictionAction#LOCAL_DESTROY},
* {@link EvictionAction#OVERFLOW_TO_DISK}
*/
public EvictionAction getEvictionAction() {
return this.evictionAction;
}
/**
* For internal use only. Returns a helper object used internally by the GemFire cache
* implementation.
*/
public final EnableLRU getLRUHelper() {
synchronized (this) {
// Synchronize with readObject/writeObject to avoid race
// conditions with copy sharing. See bug 31047.
return this.helper;
}
}
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
synchronized (this) { // See bug 31047
out.writeObject(this.evictionAction);
}
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
synchronized (this) { // See bug 31047
this.evictionAction = (EvictionAction) in.readObject();
this.helper = createLRUHelper();
}
}
// public void writeExternal(ObjectOutput out)
// throws IOException {
// out.writeObject(this.evictionAction);
// }
// public void readExternal(ObjectInput in)
// throws IOException, ClassNotFoundException {
// String evictionAction = (String) in.readObject();
// this.setEvictionAction(evictionAction);
// }
// protected Object readResolve() throws ObjectStreamException {
// if (this.helper == null) {
// this.helper = createLRUHelper();
// }
// return this;
// }
/**
* Creates a new <code>LRUHelper</code> tailed for this LRU algorithm implementation.
*/
protected abstract EnableLRU createLRUHelper();
/**
* Returns the "limit" as defined by this LRU algorithm
*/
public abstract long getLimit();
/**
* Set the limiting parameter used to determine when eviction is needed.
*/
public abstract void setLimit(int maximum);
/**
* This method is an artifact when eviction controllers used to called capacity controllers and
* were configured in the cache.xml file as <code>Declarable</code>
*
* @since GemFire 4.1.1
*/
public abstract Properties getProperties();
/**
* Releases resources obtained by this <code>LRUAlgorithm</code>
*/
public void close() {
if (this.stats != null) {
if (bucketRegion != null) {
this.stats.incEvictions(bucketRegion.getEvictions() * -1);
this.stats.decrementCounter(bucketRegion.getCounter());
bucketRegion.close();
} else {
this.stats.close();
}
}
}
/**
* Returns a copy of this LRU-based eviction controller. This method is a artifact when capacity
* controllers were used on a <code>Region</code>
*/
@Override
public Object clone() throws CloneNotSupportedException {
synchronized (this) {
LRUAlgorithm clone = (LRUAlgorithm) super.clone();
clone.stats = null;
synchronized (clone) {
clone.helper = clone.createLRUHelper();
}
return clone;
}
}
/**
* Return true if the specified capacity controller is compatible with this
*/
@Override
public boolean equals(Object cc) {
if (cc == null)
return false;
if (!getClass().isAssignableFrom(cc.getClass()))
return false;
LRUAlgorithm other = (LRUAlgorithm) cc;
if (!other.evictionAction.equals(this.evictionAction))
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() {
return this.evictionAction.hashCode();
}
/**
* Force subclasses to have a reasonable <code>toString</code>
*
* @since GemFire 4.0
*/
@Override
public abstract String toString();
////////////////////// Inner Classes //////////////////////
/**
* A partial implementation of the <code>EnableLRU</code> interface that contains code common to
* all <code>LRUAlgorithm</code>s.
*/
protected abstract class AbstractEnableLRU implements EnableLRU {
/** The region whose capacity is controller by this eviction controller */
private volatile transient String regionName;
public long limit() {
if (stats == null) {
throw new InternalGemFireException(
LocalizedStrings.LRUAlgorithm_LRU_STATS_IN_EVICTION_CONTROLLER_INSTANCE_SHOULD_NOT_BE_NULL
.toLocalizedString());
}
if (bucketRegion != null) {
return bucketRegion.getLimit();
}
return stats.getLimit();
}
public String getRegionName() {
return this.regionName;
}
public void setRegionName(Object region) {
String fullPathName;
if (region instanceof Region) {
fullPathName = ((Region) region).getFullPath();
} else if (region instanceof PlaceHolderDiskRegion) {
PlaceHolderDiskRegion phdr = (PlaceHolderDiskRegion) region;
if (phdr.isBucket()) {
fullPathName = phdr.getPrName();
} else {
fullPathName = phdr.getName();
}
} else {
throw new IllegalStateException("expected Region or PlaceHolderDiskRegion");
}
if (this.regionName != null && !this.regionName.equals(fullPathName)) {
throw new IllegalArgumentException(
LocalizedStrings.LRUAlgorithm_LRU_EVICTION_CONTROLLER_0_ALREADY_CONTROLS_THE_CAPACITY_OF_1_IT_CANNOT_ALSO_CONTROL_THE_CAPACITY_OF_REGION_2
.toLocalizedString(
new Object[] {LRUAlgorithm.this, this.regionName, fullPathName}));
}
this.regionName = fullPathName; // store the name not the region since
// region is not fully constructed yet
}
protected void setStats(LRUStatistics stats) {
LRUAlgorithm.this.stats = stats;
}
public LRUStatistics initStats(Object region, StatisticsFactory sf) {
setRegionName(region);
final LRUStatistics stats = new LRUStatistics(sf, getRegionName(), this);
stats.setLimit(LRUAlgorithm.this.getLimit());
stats.setDestroysLimit(1000);
setStats(stats);
return stats;
}
public LRUStatistics getStats() {
return LRUAlgorithm.this.stats;
}
public EvictionAction getEvictionAction() {
return LRUAlgorithm.this.evictionAction;
}
public void afterEviction() {
// Do nothing
}
}
}