package org.hypergraphdb.transaction;
import java.util.List;
import org.hypergraphdb.util.HGSortedSet;
import org.hypergraphdb.util.RefCountedMap;
import org.hypergraphdb.util.RefResolver;
public class TxCacheSet<Key, E> extends TxSet<E>
{
private Key key;
private RefCountedMap<Key,SetTxBox<E>> writeMap;
private RefResolver<Key, ? extends HGSortedSet<E>> loader;
@SuppressWarnings("unchecked")
VBoxBody<HGSortedSet<E>> insertBody(long txNumber, HGSortedSet<E> x)
{
txManager.COMMIT_LOCK.lock();
try
{
if (txNumber >= txManager.mostRecentRecord.transactionNumber) // is this the latest value
{
// a marker that we have loaded some older values for older, but still running tx
if (S.body.version == -1)
{
S.body = S.makeNewBody(x, txNumber, S.body.next);
}
else if (S.body.version < txNumber) // otherwise we just add as the newest body with the current tx number
S.body = S.makeNewBody(x, txNumber, S.body);
return S.body;
}
// if not current, we must insert it into the list of bodies
else
{
if (S.body.version == -1)
{
// We want to avoid endlessly loading the same version from disk in case
// we always load into a transaction that doesn't have the latest number, but
// the value hasn't actually changed. The 'S.loadedAt' field marks the transaction
// number when the box was first loaded so if there are no commits between S.loadedAt
// and now (which a top version of -1 indicates) then the latest possible version
// is S.loadedAt. So if txNumber>=S.loadedAt we should
// mark this version as the latest
if (txNumber >= ((CacheSetTxBox<Key, E>)S).loadedAt)
{
S.body = S.makeNewBody(x, ((CacheSetTxBox<Key, E>)S).loadedAt, S.body.next);
return S.body;
}
}
VBoxBody<HGSortedSet<E>> currentBody = S.body;
while (currentBody.next != null && currentBody.next.version > txNumber)
currentBody = currentBody.next;
// Could happen that we unnecessarily loaded the same set twice due to race conditions
// so at least we avoid storing it twice.
if (currentBody.next != null && currentBody.next.version == txNumber)
return currentBody.next;
// we need to insert b/w currentBody and currentBody.next
VBoxBody<HGSortedSet<E>> newBody = S.makeNewBody(x, txNumber, currentBody.next);
currentBody.setNext(newBody);
return newBody;
}
}
finally
{
txManager.COMMIT_LOCK.unlock();
}
}
VBoxBody<HGSortedSet<E>> load(long txNumber)
{
HGSortedSet<E> x = loader.resolve(key);
return insertBody(txNumber, x);
//return x;
}
@Override
HGSortedSet<E> read()
{
HGTransaction tx = txManager.getContext().getCurrent();
if (tx == null)
return S.body.value;
HGSortedSet<E> x = tx.getLocalValue(S);
if (x == null) // no local value, we get the correct version if loaded or return null if not
{
VBoxBody<HGSortedSet<E>> b = S.body;
// if the current transaction is not older than the top body we return it:
// it will be null if we need a load from disk or the latest committed value
// which would be the correct version
if (b.version <= tx.getNumber())
{
if (b.value == null)
b = load(tx.getNumber());
}
else
{
// else try to find the exact same version as the current transaction
while (b.version > tx.getNumber() && b.next != null)
b = b.next;
if (b.version != tx.getNumber())
b = load(tx.getNumber());
}
if (!tx.isReadOnly())
tx.bodiesRead.put(S, b);
return b.value;
}
else
{
return x == HGTransaction.NULL_VALUE ? null : x;
}
}
@Override
HGSortedSet<E> write()
{
List<LogEntry> log = txManager.getContext().getCurrent().getAttribute(S);
if (log == null) // should we copy-on-write or have we done so already?
{
HGSortedSet<E> readOnly = read(); // S.getForWrite();
HGSortedSet<E> writeable = cloneSet(readOnly);
S.put(writeable);
writeMap.put(key, S);
}
return S.get();
}
public TxCacheSet(final HGTransactionManager txManager,
final HGSortedSet<E> backingSet,
final Key key,
final RefResolver<Key, ? extends HGSortedSet<E>> loader,
final RefCountedMap<Key, SetTxBox<E>> writeMap)
{
this.txManager = txManager;
this.key = key;
this.loader = loader;
this.writeMap = writeMap;
HGTransaction tx = txManager.getContext().getCurrent();
if (tx == null)
{
S = new CacheSetTxBox<Key, E>(txManager, backingSet, this);
return;
}
S = writeMap.get(key);
long txNumber = tx.getNumber();
if (S == null)
{
S = new CacheSetTxBox<Key, E>(txManager, backingSet, this);
// we need to tag the body with the current transaction's version
// since body.version is final, we replace with a new body
S.body = S.makeNewBody(backingSet, txNumber, null);
// if this is an old transaction, we need to put null at the top of the body
// list so the latest will get reloaded if needed
if (txNumber < txManager.mostRecentRecord.transactionNumber)
S.body = S.makeNewBody(null, -1, S.body);
}
else
insertBody(txNumber, backingSet);
}
public static class CacheSetTxBox<Key, E> extends SetTxBox<E>
{
long loadedAt;
CacheSetTxBox(final HGTransactionManager txManager,
final HGSortedSet<E> backingSet,
final TxSet<E> thisSet)
{
super(txManager, backingSet, thisSet);
loadedAt = txManager.mostRecentRecord.transactionNumber;
}
@SuppressWarnings("unchecked")
HGSortedSet<E> getLastCommitted(HGTransaction tx)
{
TxCacheSet<Key, E> s = (TxCacheSet<Key, E>)thisSet;
HGSortedSet<E> lastCommitted = super.getLastCommitted(tx);
return (lastCommitted == null) ? s.load(tx.getNumber()).value : lastCommitted;
}
@Override
public VBoxBody<HGSortedSet<E>> commit(HGTransaction tx, HGSortedSet<E> newvalue, long txNumber)
{
VBoxBody<HGSortedSet<E>> latest = super.commit(tx, newvalue, txNumber);
// check if we have the special "old value" marker hanging in the second place of
// the list of bodies after the commit, and if so unlink it
if (latest.next != null && latest.next.version == -1)
latest.setNext(latest.next.next);
return latest;
}
@SuppressWarnings("unchecked")
@Override
public void finish(HGTransaction tx)
{
if (tx.getAttribute(this) != null)
{
TxCacheSet<Key, E> s = (TxCacheSet<Key, E>)thisSet;
s.writeMap.remove(s.key);
}
}
}
}