/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-06 Wolfgang M. Meier
* wolfgang@exist-db.org
* http://exist.sourceforge.net
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
package org.exist.storage.cache;
import org.exist.storage.CacheManager;
import org.exist.util.hashtable.Long2ObjectHashMap;
/**
* Cache implementation based on the GClock algorithm.
*
* Implements a mixture between LFU (Last Frequently Used) and LRU
* (Last Recently Used) replacement policies. The class
* uses reference counts to track references to cached objects. Each call to the add
* method increments the reference count of the object.
*
* If the cache is full, the object to be removed is determined by decrementing the
* reference count for each object until an object with reference count = 0 is found.
*
* The implementation tends to replace younger objects first.
*
* @author wolf
*/
public class GClockCache implements Cache {
protected Cacheable[] items;
protected int count = 0;
protected int size;
protected Long2ObjectHashMap map;
protected int used = 0;
protected int hitsOld = 0;
protected Accounting accounting;
protected double growthFactor;
protected CacheManager cacheManager = null;
private String fileName = "unknown";
private String type;
public GClockCache(int size, double growthFactor, double growthThreshold, String type) {
this.size = size;
this.growthFactor = growthFactor;
this.items = new Cacheable[size];
this.map = new Long2ObjectHashMap(size * 2);
accounting = new Accounting(growthThreshold);
accounting.setTotalSize(size);
this.type = type;
}
public String getType() {
return type;
}
public void add(Cacheable item) {
add(item, 1);
}
public void add(Cacheable item, int initialRefCount) {
Cacheable old = (Cacheable) map.get(item.getKey());
if (old != null) {
old.incReferenceCount();
return;
}
item.setReferenceCount(initialRefCount);
if (count < size) {
items[count++] = item;
map.put(item.getKey(), item);
used++;
} else {
removeOne(item);
}
}
public Cacheable get(Cacheable item) {
return get(item.getKey());
}
public Cacheable get(long key) {
Cacheable item = (Cacheable) map.get(key);
if (item == null) {
accounting.missesIncrement();
} else
accounting.hitIncrement();
return item;
}
public void remove(Cacheable item) {
long key = item.getKey();
Cacheable cacheable = (Cacheable) map.remove(key);
if (cacheable == null)
return;
for (int i = 0; i < count; i++) {
if (items[i] != null && items[i].getKey() == key) {
items[i] = null;
used--;
return;
}
}
LOG.error("item not found in list");
}
public boolean flush() {
boolean flushed = false;
int written = 0;
for (int i = 0; i < count; i++) {
if (items[i] != null && items[i].sync(false)) {
++written;
flushed = true;
}
}
//LOG.debug(written + " pages written to disk");
return flushed;
}
public boolean hasDirtyItems() {
for(int i = 0; i < count; i++) {
if(items[i] != null && items[i].isDirty())
return true;
}
return false;
}
protected Cacheable removeOne(Cacheable item) {
Cacheable old = null;
boolean removed = false;
int bucket;
do {
bucket = -1;
// decrease all reference counts by 1
for (int i = 0; i < count; i++) {
old = items[i];
if (old == null) {
bucket = i;
} else if (old.decReferenceCount() < 1 && bucket < 0) {
bucket = i;
}
}
if (bucket > -1) {
old = items[bucket];
if (old != null) {
//LOG.debug(fileName + " replacing " + old.getKey() + " for " + item.getKey());
map.remove(old.getKey());
old.sync(true);
} else {
used++;
}
items[bucket] = item;
map.put(item.getKey(), item);
removed = true;
}
} while (!removed);
if (old != null) {
accounting.replacedPage(item);
if (cacheManager != null && accounting.resizeNeeded()) {
cacheManager.requestMem(this);
}
}
return old;
}
public int getBuffers() {
return size;
}
public int getUsedBuffers() {
return used;
}
public double getGrowthFactor() {
return growthFactor;
}
public int getHits() {
return accounting.getHits();
}
public int getFails() {
return accounting.getMisses();
}
public int getThrashing() {
return accounting.getThrashing();
}
public void setCacheManager(CacheManager manager) {
this.cacheManager = manager;
}
public void resize(int newSize) {
if (newSize < size) {
shrink(newSize);
} else {
Cacheable[] newItems = new Cacheable[newSize];
Long2ObjectHashMap newMap = new Long2ObjectHashMap(newSize * 2);
for (int i = 0; i < count; i++) {
newItems[i] = items[i];
newMap.put(items[i].getKey(), items[i]);
}
this.size = newSize;
this.map = newMap;
this.items = newItems;
accounting.reset();
accounting.setTotalSize(size);
}
}
protected void shrink(int newSize) {
flush();
items = new Cacheable[newSize];
map = new Long2ObjectHashMap(newSize * 2);
size = newSize;
count = 0;
used = 0;
accounting.reset();
accounting.setTotalSize(size);
}
public int getLoad() {
if (hitsOld == 0) {
hitsOld = accounting.getHits();
return Integer.MAX_VALUE;
}
int load = accounting.getHits() - hitsOld;
hitsOld = accounting.getHits();
return load;
}
public void setFileName(String name) {
fileName = name;
}
public String getFileName() {
return fileName;
}
}