blob: 5f22eb1f3fae14d0e9ba2dc31bcb1177c9b8684d [file] [log] [blame]
/*
* Copyright (c) 2011, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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 com.google.dart.indexer.pagedstorage.util;
import com.google.dart.indexer.IndexerPlugin;
import com.google.dart.indexer.debug.IndexerDebugOptions;
import com.google.dart.indexer.pagedstorage.DebugConstants;
import com.google.dart.indexer.pagedstorage.exceptions.PagedStorageException;
import com.google.dart.indexer.storage.paged.store.CacheObject;
import com.google.dart.indexer.storage.paged.store.CacheWriter;
/**
* A cache implementation based on the last recently used (LRU) algorithm.
*/
public class CacheLRU implements Cache {
static final String TYPE_NAME = "LRU";
private final CacheWriter writer;
private final CacheObject head = new CacheHead();
private final int len;
private final int mask;
private int maxSize;
private CacheObject[] values;
private int recordCount;
private int sizeMemory;
private CacheLRU(CacheWriter writer, int maxKb) {
this.maxSize = maxKb * 1024 / 4;
this.writer = writer;
this.len = MathUtils.nextPowerOf2(maxSize / 64);
this.mask = len - 1;
MathUtils.checkPowerOf2(len);
clear();
}
@Override
public void clear() {
head.next = head.previous = head;
// first set to null - avoiding out of memory
values = null;
values = new CacheObject[len];
recordCount = 0;
sizeMemory = 0;
}
@Override
public CacheObject find(int pos) {
CacheObject rec = values[pos & mask];
while (rec != null && rec.getPos() != pos) {
rec = rec.chained;
}
return rec;
}
@Override
public CacheObject get(int pos) {
CacheObject rec = find(pos);
if (rec != null) {
removeFromLinkedList(rec);
addToFront(rec);
}
return rec;
}
@Override
public ObjectArray<CacheObject> getAllChanged() {
// TODO cache: should probably use the LRU list
ObjectArray<CacheObject> list = ObjectArray.newInstance();
for (int i = 0; i < len; i++) {
CacheObject rec = values[i];
while (rec != null) {
if (rec.isChanged()) {
list.add(rec);
if (list.size() >= recordCount) {
if (DebugConstants.CHECK) {
if (list.size() > recordCount) {
throw new AssertionError("cache chain error");
}
} else {
break;
}
}
}
rec = rec.chained;
}
}
return list;
}
@Override
public int getMaxSize() {
return maxSize;
}
@Override
public int getSize() {
return sizeMemory;
}
@Override
public String getTypeName() {
return TYPE_NAME;
}
@Override
public void put(CacheObject rec) throws PagedStorageException {
if (DebugConstants.CHECK) {
// int pos = rec.getPos();
// for (int i = 0; i < rec.getBlockCount(); i++) {
// CacheObject old = find(pos + i);
// if (old != null) {
// Message.throwInternalError("try to add a record twice pos:" + pos +
// " i:" + i);
// }
// }
}
int index = rec.getPos() & mask;
rec.chained = values[index];
values[index] = rec;
recordCount++;
sizeMemory += rec.getMemorySize();
addToFront(rec);
removeOldIfRequired();
}
@Override
public void remove(int pos) {
int index = pos & mask;
CacheObject rec = values[index];
if (rec == null) {
return;
}
if (rec.getPos() == pos) {
values[index] = rec.chained;
} else {
CacheObject last;
do {
last = rec;
rec = rec.chained;
if (rec == null) {
return;
}
} while (rec.getPos() != pos);
last.chained = rec.chained;
}
recordCount--;
sizeMemory -= rec.getMemorySize();
removeFromLinkedList(rec);
if (DebugConstants.CHECK) {
rec.chained = null;
if (find(pos) != null) {
throw new AssertionError("not removed!");
}
}
}
@Override
public void setMaxSize(int maxKb) throws PagedStorageException {
int newSize = maxKb * 1024 / 4;
maxSize = newSize < 0 ? 0 : newSize;
// can not resize, otherwise existing records are lost
// resize(maxSize);
removeOldIfRequired();
}
@Override
public CacheObject update(int pos, CacheObject rec) throws PagedStorageException {
CacheObject old = find(pos);
if (old == null) {
put(rec);
} else {
if (DebugConstants.CHECK) {
if (old != rec) {
throw new IllegalArgumentException(
"Attemp to write conflicting records into cache - pos:" + pos + " old:" + old
+ " new:" + rec);
}
}
removeFromLinkedList(rec);
addToFront(rec);
}
return old;
}
private void addToFront(CacheObject rec) {
if (DebugConstants.CHECK && rec == head) {
throw new IllegalArgumentException("try to move head");
}
rec.next = head;
rec.previous = head.previous;
rec.previous.next = rec;
head.previous = rec;
}
private void removeFromLinkedList(CacheObject rec) {
if (DebugConstants.CHECK && rec == head) {
throw new IllegalArgumentException("try to remove head");
}
rec.previous.next = rec.next;
rec.next.previous = rec.previous;
// TODO cache: mystery: why is this required? needs more memory if we
// don't do this
rec.next = null;
rec.previous = null;
}
private void removeOld() throws PagedStorageException {
int i = 0;
ObjectArray<CacheObject> changed = ObjectArray.newInstance();
while (sizeMemory * 4 > maxSize * 3 && recordCount > Constants.CACHE_MIN_RECORDS) {
i++;
if (i >= recordCount * 2) {
// hopefully this does not happen too much, but it could happen
// theoretically
IndexerPlugin.getLogger().trace(IndexerDebugOptions.RARE_ANOMALIES,
"Cannot remove records, cache size too small?");
break;
}
CacheObject last = head.next;
if (DebugConstants.CHECK && last == head) {
throw new AssertionError("try to remove head");
}
// we are not allowed to remove it if the log is not yet written
// (because we need to log before writing the data)
// also, can't write it if the record is pinned
if (!last.canRemove()) {
removeFromLinkedList(last);
addToFront(last);
continue;
}
remove(last.getPos());
if (last.isChanged()) {
changed.add(last);
}
}
if (changed.size() > 0) {
CacheObject.sort(changed);
for (i = 0; i < changed.size(); i++) {
CacheObject rec = changed.get(i);
writer.writeBack(rec);
}
}
}
private void removeOldIfRequired() throws PagedStorageException {
// a small method, to allow inlining
if (sizeMemory >= maxSize) {
removeOld();
}
}
}