/* * 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.StatisticsFactory; import org.apache.geode.cache.Region; import org.apache.geode.distributed.internal.DistributionConfig; import org.apache.geode.internal.cache.*; import org.apache.geode.internal.cache.versions.RegionVersionVector; import org.apache.geode.internal.i18n.LocalizedStrings; import org.apache.geode.internal.logging.LogService; import org.apache.geode.internal.logging.log4j.LocalizedMessage; import org.apache.geode.internal.logging.log4j.LogMarker; import org.apache.logging.log4j.Logger; /** * AbstractLRUClockHand holds the lrulist, and the behavior for maintaining the list in a cu-pipe * and determining the next entry to be removed. Each EntriesMap that supports LRU holds one of * these. */ public class NewLRUClockHand { private static final Logger logger = LogService.getLogger(); private BucketRegion bucketRegion = null; /** The last node in the LRU list after which all new nodes are added */ protected LRUClockNode tail = new GuardNode(); /** The starting point in the LRU list for searching for the LRU node */ protected LRUClockNode head = new GuardNode(); /** The object for locking the head of the cu-pipe. */ final protected HeadLock lock; /** Description of the Field */ final private LRUStatistics stats; /** Counter for the size of the LRU list */ protected int size = 0; public static final boolean debug = Boolean.getBoolean(DistributionConfig.GEMFIRE_PREFIX + "verbose-lru-clock"); static private final int maxEntries; static { String squelch = System.getProperty(DistributionConfig.GEMFIRE_PREFIX + "lru.maxSearchEntries"); if (squelch == null) maxEntries = -1; else maxEntries = Integer.parseInt(squelch); } /** only used by enhancer */ // protected NewLRUClockHand( ) { } // private long size = 0; public NewLRUClockHand(Object region, EnableLRU ccHelper, InternalRegionArguments internalRegionArgs) { setBucketRegion(region); this.lock = new HeadLock(); // behavior relies on a single evicted node in the pipe when the pipe is empty. initHeadAndTail(); if (this.bucketRegion != null) { this.stats = internalRegionArgs.getPartitionedRegion() != null ? internalRegionArgs.getPartitionedRegion().getEvictionController().stats : null; } else { LRUStatistics tmp = null; if (region instanceof PlaceHolderDiskRegion) { tmp = ((PlaceHolderDiskRegion) region).getPRLRUStats(); } else if (region instanceof PartitionedRegion) { tmp = ((PartitionedRegion) region).getPRLRUStatsDuringInitialization(); // bug 41938 PartitionedRegion pr = (PartitionedRegion) region; if (tmp != null) { pr.getEvictionController().stats = tmp; } } if (tmp == null) { StatisticsFactory sf = GemFireCacheImpl.getExisting("").getDistributedSystem(); tmp = ccHelper.initStats(region, sf); } this.stats = tmp; } } public void setBucketRegion(Object r) { if (r instanceof BucketRegion) { this.bucketRegion = (BucketRegion) r; // see bug 41388 } } public NewLRUClockHand(Region region, EnableLRU ccHelper, NewLRUClockHand oldList) { setBucketRegion(region); this.lock = new HeadLock(); // behavior relies on a single evicted node in the pipe when the pipe is empty. initHeadAndTail(); if (oldList.stats == null) { // see bug 41388 StatisticsFactory sf = region.getCache().getDistributedSystem(); this.stats = ccHelper.initStats(region, sf); } else { this.stats = oldList.stats; if (this.bucketRegion != null) { this.stats.decrementCounter(this.bucketRegion.getCounter()); this.bucketRegion.resetCounter(); } else { this.stats.resetCounter(); } } } /** Description of the Method */ public void close() { closeStats(); if (bucketRegion != null) bucketRegion.close(); } public void closeStats() { LRUStatistics ls = this.stats; if (ls != null) { ls.close(); } } /** * Adds a new lru node for the entry between the current tail and head of the list. * * @param aNode Description of the Parameter */ public final void appendEntry(final LRUClockNode aNode) { synchronized (this.lock) { if (aNode.nextLRUNode() != null || aNode.prevLRUNode() != null) { return; } if (logger.isTraceEnabled(LogMarker.LRU_CLOCK)) { logger.trace(LogMarker.LRU_CLOCK, LocalizedMessage .create(LocalizedStrings.NewLRUClockHand_ADDING_ANODE_TO_LRU_LIST, aNode)); } aNode.setNextLRUNode(this.tail); this.tail.prevLRUNode().setNextLRUNode(aNode); aNode.setPrevLRUNode(this.tail.prevLRUNode()); this.tail.setPrevLRUNode(aNode); this.size++; } } /** * return the head entry in the list preserving the cupipe requirement of at least one entry left * in the list */ private LRUClockNode getHeadEntry() { synchronized (lock) { LRUClockNode aNode = NewLRUClockHand.this.head.nextLRUNode(); if (aNode == this.tail) { return null; } LRUClockNode next = aNode.nextLRUNode(); this.head.setNextLRUNode(next); next.setPrevLRUNode(this.head); aNode.setNextLRUNode(null); aNode.setPrevLRUNode(null); this.size--; return aNode; } } /** * return the Entry that is considered least recently used. The entry will no longer be in the * pipe (unless it is the last empty marker). */ public LRUClockNode getLRUEntry() { long numEvals = 0; for (;;) { LRUClockNode aNode = null; aNode = getHeadEntry(); if (logger.isTraceEnabled(LogMarker.LRU_CLOCK)) { logger.trace(LogMarker.LRU_CLOCK, "lru considering {}", aNode); } if (aNode == null) { // hit the end of the list this.stats.incEvaluations(numEvals); return aNode; } // hit the end of the list numEvals++; // If this Entry is part of a transaction, skip it since // eviction should not cause commit conflicts synchronized (aNode) { if (aNode instanceof AbstractRegionEntry) { if (((AbstractRegionEntry) aNode).isInUseByTransaction()) { if (logger.isTraceEnabled(LogMarker.LRU_CLOCK)) { logger.trace(LogMarker.LRU_CLOCK, LocalizedMessage.create( LocalizedStrings.NewLRUClockHand_REMOVING_TRANSACTIONAL_ENTRY_FROM_CONSIDERATION)); } continue; } } if (aNode.testEvicted()) { if (logger.isTraceEnabled(LogMarker.LRU_CLOCK)) { logger.trace(LogMarker.LRU_CLOCK, LocalizedMessage.create(LocalizedStrings.NewLRUClockHand_DISCARDING_EVICTED_ENTRY)); } continue; } // At this point we have any acceptable entry. Now // use various criteria to determine if it's good enough // to return, or if we need to add it back to the list. if (maxEntries > 0 && numEvals > maxEntries) { if (logger.isTraceEnabled(LogMarker.LRU_CLOCK)) { logger.trace(LogMarker.LRU_CLOCK, LocalizedMessage .create(LocalizedStrings.NewLRUClockHand_GREEDILY_PICKING_AN_AVAILABLE_ENTRY)); } this.stats.incGreedyReturns(1); // fall through, return this node } else if (aNode.testRecentlyUsed()) { // Throw it back, it's in the working set aNode.unsetRecentlyUsed(); // aNode.setInList(); if (logger.isTraceEnabled(LogMarker.LRU_CLOCK)) { logger.trace(LogMarker.LRU_CLOCK, LocalizedMessage .create(LocalizedStrings.NewLRUClockHand_SKIPPING_RECENTLY_USED_ENTRY, aNode)); } appendEntry(aNode); continue; // keep looking } else { if (logger.isTraceEnabled(LogMarker.LRU_CLOCK)) { logger.trace(LogMarker.LRU_CLOCK, LocalizedMessage .create(LocalizedStrings.NewLRUClockHand_RETURNING_UNUSED_ENTRY, aNode)); } // fall through, return this node } // Return the current node. this.stats.incEvaluations(numEvals); return aNode; } // synchronized } // for } public void dumpList() { final boolean isDebugEnabled = logger.isTraceEnabled(LogMarker.LRU_CLOCK); if (!isDebugEnabled) { return; } synchronized (lock) { int idx = 1; for (LRUClockNode aNode = this.head; aNode != null; aNode = aNode.nextLRUNode()) { if (isDebugEnabled) { logger.trace(LogMarker.LRU_CLOCK, " ({}) {}", (idx++), aNode); } } } } public long getExpensiveListCount() { synchronized (lock) { long count = 0; for (LRUClockNode aNode = this.head.nextLRUNode(); aNode != this.tail; aNode = aNode.nextLRUNode()) { count++; } return count; } } public String getAuditReport() { LRUClockNode h = this.head; int totalNodes = 0; int evictedNodes = 0; int usedNodes = 0; while (h != null) { totalNodes++; if (h.testEvicted()) evictedNodes++; if (h.testRecentlyUsed()) usedNodes++; h = h.nextLRUNode(); } StringBuffer result = new StringBuffer(128); result.append("LRUList Audit: listEntries = ").append(totalNodes).append(" evicted = ") .append(evictedNodes).append(" used = ").append(usedNodes); return result.toString(); } /** unsynchronized audit...only run after activity has ceased. */ public void audit() { System.out.println(getAuditReport()); } /** remove an entry from the pipe... (marks it evicted to be skipped later) */ public boolean unlinkEntry(LRUClockNode entry) { if (logger.isTraceEnabled(LogMarker.LRU_CLOCK)) { logger.trace(LogMarker.LRU_CLOCK, LocalizedMessage.create(LocalizedStrings.NewLRUClockHand_UNLINKENTRY_CALLED, entry)); } entry.setEvicted(); stats().incDestroys(); synchronized (lock) { LRUClockNode next = entry.nextLRUNode(); LRUClockNode prev = entry.prevLRUNode(); if (next == null || prev == null) { // not in the list anymore. return false; } next.setPrevLRUNode(prev); prev.setNextLRUNode(next); entry.setNextLRUNode(null); entry.setPrevLRUNode(null); this.size--; } return true; } /** * Get the modifier for lru based statistics. * * @return The LRUStatistics for this Clock hand's region. */ public LRUStatistics stats() { return this.stats; } /** * called when an LRU map is cleared... resets stats and releases prev and next. */ public void clear(RegionVersionVector rvv) { if (rvv != null) { return; // when concurrency checks are enabled the clear operation removes entries iteratively } synchronized (this.lock) { if (bucketRegion != null) { this.stats.decrementCounter(bucketRegion.getCounter()); bucketRegion.resetCounter(); } else { this.stats.resetCounter(); } initHeadAndTail(); // LRUClockNode node = this.tail; // node.setEvicted(); // // // NYI need to walk the list and call unsetInList for each one. // // // tail's next should already be null. // setHead( node ); } } private void initHeadAndTail() { // I'm not sure, but I think it's important that we // drop the references to the old head and tail on a region clear // That will prevent any concurrent operations that are messing // with existing nodes from screwing up the head and tail after // the clear. // Dan 9/23/09 this.head = new GuardNode(); this.tail = new GuardNode(); this.head.setNextLRUNode(this.tail); this.tail.setPrevLRUNode(this.head); this.size = 0; } /** * Get size of LRU queue * * @return size */ public int size() { return size; } /** perform work of clear(), after subclass has properly synchronized */ // private void internalClear() { // stats().resetCounter(); // LRUClockNode node = this.tail; // node.setEvicted(); // // // NYI need to walk the list and call unsetInList for each one. // // // tail's next should already be null. // setHead( node ); // } /** Marker class name to identify the lock more easily in thread dumps */ protected static class HeadLock extends Object { } private static final class GuardNode implements LRUClockNode { private LRUClockNode next; LRUClockNode prev; public int getEntrySize() { return 0; } public LRUClockNode nextLRUNode() { return next; } public LRUClockNode prevLRUNode() { return prev; } public void setEvicted() { } public void setNextLRUNode(LRUClockNode next) { this.next = next; } public void setPrevLRUNode(LRUClockNode prev) { this.prev = prev; } public void setRecentlyUsed() {} public boolean testEvicted() { return false; } public boolean testRecentlyUsed() { return false; } public void unsetEvicted() {} public void unsetRecentlyUsed() {} public int updateEntrySize(EnableLRU ccHelper) { return 0; } public int updateEntrySize(EnableLRU ccHelper, Object value) { return 0; } } }