/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source 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, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.db.index;
import com.caucho.util.*;
import com.caucho.db.xa.DbTransaction;
import com.caucho.env.thread.TaskWorker;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.*;
import java.sql.SQLException;
/**
* Manages the block cache
*/
public final class IndexCache
{
private static final Logger log
= Logger.getLogger(IndexCache.class.getName());
private static final L10N L = new L10N(IndexCache.class);
private static IndexCache _staticCache;
private final LruCache<IndexKey,IndexKey> _cache;
private final ArrayList<IndexKey> _writeQueue
= new ArrayList<IndexKey>();
private final AtomicReference<IndexKey> _freeKey
= new AtomicReference<IndexKey>();
private IndexCacheWriter _indexWriter = new IndexCacheWriter();
private IndexCache(int capacity)
{
_cache = new LruCache<IndexKey,IndexKey>(capacity);
}
/**
* Returns the block manager, ensuring a minimum number of entries.
*/
public static IndexCache create()
{
if (_staticCache == null) {
int size;
if (Alarm.isTest())
size = 8 * 1024;
else
size = 64 * 1024;
_staticCache = new IndexCache(size);
}
return _staticCache;
}
public static IndexCache getCurrent()
{
return _staticCache;
}
/**
* Gets the index entry.
*/
public long lookup(BTree btree,
byte []buffer, int offset, int length,
DbTransaction xa)
throws SQLException
{
IndexKey value = lookupValue(btree, buffer, offset, length);
if (value != null && value.isValid()) {
return value.getValue();
}
long btreeValue;
try {
btreeValue = btree.lookup(buffer, offset, length);
} catch (IOException e) {
throw new SQLException(e);
}
value = IndexKey.create(btree, buffer, offset, length, btreeValue);
value.setValid(true);
_cache.compareAndPut(null, value, value);
return btreeValue;
}
/**
* Gets the index entry.
*/
public void insert(BTree btree,
byte []buffer, int offset, int length,
long value,
DbTransaction xa)
throws SQLException
{
IndexKey key = IndexKey.create(btree, buffer, offset, length, value);
if (! _cache.compareAndPut(null, key, key)) {
// XXX:
throw new SQLException(L.l("duplicate key exception"));
}
long btreeValue;
try {
btreeValue = btree.lookup(buffer, offset, length);
} catch (IOException e) {
throw new SQLException(e);
}
if (btreeValue != 0) {
key.setValue(btreeValue);
key.setValid(true);
throw new SQLException(L.l("duplicate key exception"));
}
key.setValid(true);
}
/**
* Remove the index entry.
*/
public void delete(BTree btree,
byte []buffer, int offset, int length,
DbTransaction xa)
throws SQLException
{
IndexKey value = lookupValue(btree, buffer, offset, length);
if (value != null) {
value.setValue(0); // any updates will get written by the thread
}
else {
btree.remove(buffer, offset, length);
}
}
private IndexKey lookupValue(BTree btree,
byte []buffer, int offset, int length)
{
IndexKey key = _freeKey.getAndSet(null);
if (key == null)
key = new IndexKey();
key.init(btree, buffer, offset, length);
IndexKey value = _cache.get(key);
if (value == null) {
synchronized (_writeQueue) {
int size = _writeQueue.size();
for (int i = 0; i < size; i++) {
IndexKey writeKey = _writeQueue.get(i);
if (key.equals(writeKey)) {
value = writeKey;
_cache.compareAndPut(null, value, value);
}
}
}
}
_freeKey.set(key);
return value;
}
/**
* Adds a block that's needs to be flushed.
*/
void addWrite(IndexKey key)
{
key.setStored(true);
while (_writeQueue.size() > 1024) {
_indexWriter.wake();
synchronized (_writeQueue) {
if (_writeQueue.size() > 1024) {
try {
_writeQueue.wait(1000);
} catch (Exception e) {
log.log(Level.FINEST, e.toString(), e);
}
}
}
}
synchronized (_writeQueue) {
_writeQueue.add(key);
}
_indexWriter.wake();
}
class IndexCacheWriter extends TaskWorker {
public long runTask()
{
DbTransaction xa = DbTransaction.create();
try {
IndexKey key = null;
Thread.interrupted();
synchronized (_writeQueue) {
if (_writeQueue.size() == 0)
return -1;
key = _writeQueue.get(0);
}
if (key != null) {
BTree btree = key.getBTree();
long value = key.getValue();
if (! key.isStored()) {
}
else if (value != 0) {
btree.insert(key.getBuffer(), key.getOffset(), key.getLength(),
value, true);
}
else {
btree.remove(key.getBuffer(), key.getOffset(), key.getLength());
}
}
synchronized (_writeQueue) {
if (key != null)
_writeQueue.remove(0);
_writeQueue.notify();
}
} catch (Throwable e) {
log.log(Level.WARNING, e.toString(), e);
}
return -1;
}
}
}