com.bigdata.journal.Tx Maven / Gradle / Ivy
/*
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
[email protected]
This program 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; version 2 of the License.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Oct 13, 2006
*/
package com.bigdata.journal;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.log4j.Logger;
import com.bigdata.btree.AbstractBTree;
import com.bigdata.btree.BTree;
import com.bigdata.btree.ILocalBTreeView;
import com.bigdata.btree.IndexSegment;
import com.bigdata.btree.isolation.IsolatedFusedView;
import com.bigdata.rawstore.IRawStore;
import com.bigdata.resources.ResourceManager;
import com.bigdata.resources.StoreManager;
import com.bigdata.service.DataService;
import com.bigdata.service.IBigdataFederation;
import com.bigdata.service.IDataService;
/**
*
* A transaction.
*
* A transaction is a context in which the application can access and perform
* operations on fully named indices. Writes on the named indices accessed by
* the transaction are accumulated in a {@link IsolatedFusedView}s. In order to
* commit, the write set of the transaction must be validated against the then
* current state of the corresponding unisolated indices and then merge down
* onto those indices, applying the specified revisionTime. The
* transaction MUST have an exclusive lock on the named indices on which it will
* write from the that it begins validation until after the commit or abort of
* the transaction. This is necessary in order to ensure that concurrent writers
* can not invalidate the conditions under which validation is performed.
*
*
* The write set of a transaction is written onto a {@link TemporaryRawStore}.
* Therefore the size limit on the transaction write set is currently 2G, but
* the transaction will be buffered in memory until the store exceeds its write
* cache size and creates a backing file on the disk. The store is closed and
* any backing file is deleted as soon as the transaction completes.
*
*
* Each {@link IsolatedFusedView} is local to a transaction and is backed by the
* temporary store for that transaction. This means that concurrent transactions
* can execute without synchronization (real concurrency) up to the point where
* they {@link #prepare()}. We do not need a read-lock on the indices isolated
* by the transaction since they are historical states that will not
* receive concurrent updates.
*
*
* @author Bryan Thompson
*
* @todo javadoc update for temporary stores (shared temporary journals) and
* concurrency limits (concurrent operations on the same transaction with
* a partial order imposed by locks on the isolated indices and concurrent
* operations on different transactions).
*
* @todo Track which {@link IndexSegment}s and {@link Journal}s are required
* to support the {@link IsolatedFusedView}s in use by a {@link Tx}. The
* easiest way to do this is to name these by appending the transaction
* identifier to the name of the index partition, e.g., name#partId#tx. At
* that point the {@link StoreManager} will automatically track the
* resources. This also simplifies access control (write locks) for the
* isolated indices as the same {@link WriteExecutorService} will serve.
* However, with this approach {split, move, join} operations will have to
* be either deferred or issued against the isolated index partitions as
* well as the unisolated index partitions.
*
* Make writes on the tx thread-safe (Temporary mode Journal rather than
* TemporaryStore).
*
* @todo Modify the isolated indices use a delegation strategy so that I can
* trap attempts to access an isolated index once the transaction is no
* longer active? Define "active" as up to the point where a "commit" or
* "abort" is _requested_ for the tx. (Alternative, extend the close()
* model for an index to not permit the re-open of an isolated index after
* the tx commits and then just close the btree absorbing writes for the
* isolated index when we are releasing our various resources. The
* isolated index will thereafter be unusable, which is what we want.)
*/
public class Tx implements ITx {
private static final Logger log = Logger.getLogger(Tx.class);
// protected static final boolean INFO = log.isInfoEnabled();
/*
* Text for error messages.
*/
final static protected String NOT_ACTIVE = "Not active";
final static protected String NOT_PREPARED = "Transaction is not prepared";
final static protected String NOT_COMMITTED = "Transaction is not committed";
final static protected String IS_COMPLETE = "Transaction is complete";
/**
* This {@link Lock} is used to obtain exclusive access during certain
* operations, including creating the temporary store and isolating a view
* of a named index. Exclusive access is required since multiple concurrent
* operations MAY execute for the same transaction.
*
* Note: This is exposed to the {@link DataService}.
*/
final public ReentrantLock lock = new ReentrantLock();
// /**
// * Used for some handshaking in the commit protocol.
// */
// final private AbstractLocalTransactionManager localTransactionManager;
/**
* Used to locate the named indices that the transaction isolates.
*/
final private IResourceManager resourceManager;
/**
* The start startTime assigned to this transaction.
*
* Note: Transaction {@link #startTime} and {@link #revisionTime}s are
* assigned by the {@link ITransactionService}.
*/
final private long startTime;
/**
* The timestamp of the commit point against which this transaction is
* reading.
*
* Note: This is not currently available on a cluster. In that context, we
* wind up with the same timestamp for {@link #startTime} and
* {@link #readsOnCommitTime} which causes cache pollution for things which
* cache based on {@link #readsOnCommitTime}.
*
* @see
* Refactor native long tx id to thin object
*
* @see Add
* cache for access to historical index views on the Journal by name
* and commitTime.
*/
final private long readsOnCommitTime;
/**
* The pre-computed hash code for the transaction (based on the start time).
*/
final private int hashCode;
/**
* true
iff this is a read-only transaction.
*/
final private boolean readOnly;
/**
* The revisionTime assigned to the transaction when it was validated and
* merged down onto the global state.
*/
private long revisionTime = 0L;
/**
* The revisionTime assigned to the transaction when it was
* validated and merged down onto the global state.
*
* @see #mergeOntoGlobalState(long)
*/
public long getRevisionTime() {
return revisionTime;
}
@Override
public long getReadsOnCommitTime() {
return readsOnCommitTime;
}
@Override
public boolean isReadOnly() {
return readOnly;
}
private final AtomicReference runState = new AtomicReference();
// /**
// * A temporary store used to hold write sets for read-write transactions. It
// * is null if the transaction is read-only and will remain null in any case
// * until its first use.
// */
// private IRawStore tmpStore = null;
/**
* Indices isolated by this transactions.
*
* Note: This must be thread-safe to support concurrent operations for the
* same transaction (however, those operations will be serialized if they
* declare any named indices in common).
*/
private final ConcurrentHashMap indices;
/**
* Create a transaction reading from the most recent committed state not
* later than the specified startTime.
*
* Note: For an {@link IBigdataFederation}, a transaction does not start
* execution on all {@link IDataService}s at the same moment. Instead, the
* transaction startTime is assigned by the {@link ITransactionService} and
* then provided each time an {@link ITx} must be created for isolatation of
* resources accessible on a {@link IDataService}.
*
* @param transactionManager
* The local (client-side) transaction manager.
* @param resourceManager
* Provides access to named indices that are isolated by the
* transaction.
* @param startTime
* The transaction identifier
* @param readsOnCommitTime
* The timestamp of the commit point against which this
* transaction is reading.
*
* @see
* Refactor native long tx id to thin object
*
* @see Add
* cache for access to historical index views on the Journal by name
* and commitTime.
*/
public Tx(//
final AbstractLocalTransactionManager localTransactionManager,//
final IResourceManager resourceManager, //
final long startTime,
final long readsOnCommitTime//
) {
if (localTransactionManager == null)
throw new IllegalArgumentException();
if (resourceManager == null)
throw new IllegalArgumentException();
this.readOnly = !TimestampUtility.isReadWriteTx(startTime);
// if (!TimestampUtility.isReadWriteTx(startTime)) {
//
// /*
// * Note: We only maintain local state for read-write transactions.
// */
//
// throw new IllegalArgumentException();
//
// }
// this.localTransactionManager = localTransactionManager;
this.indices = readOnly ? null
: new ConcurrentHashMap();
this.resourceManager = resourceManager;
this.startTime = startTime;
/*
* Note: On a new journal, the lastCommitTime is ZERO before the first
* commit point. In order to avoid having the ground state view be the
* UNISOLATED index view, we use a timestamp which is known to be before
* any valid timestamp (and which will be shared by any transaction
* started against an empty journal).
*
* Note: It might be better to put this logic into
* Journal#getLastCommitTime(). However, there are other callers for
* that method. By making the change here, we can guarantee that its
* scope is limited to only the code change made for the following
* ticket.
*
* @see
* Add cache for access to historical index views on the Journal by name
* and commitTime.
*/
this.readsOnCommitTime = readsOnCommitTime == ITx.UNISOLATED ? 1
: readsOnCommitTime;
this.runState.set(RunState.Active);
// pre-compute the hash code for the transaction.
this.hashCode = Long.valueOf(startTime).hashCode();
localTransactionManager.activateTx(this);
// report event.
ResourceManager.openTx(startTime);
}
/**
* Change the {@link RunState}.
*
* @param newval
* The new {@link RunState}.
*
* @throws IllegalArgumentException
* if the argument is null
.
* @throws IllegalStateException
* if the state transition is not allowed.
*
* @see RunState#isTransitionAllowed(RunState)
*/
public void setRunState(final RunState newval) {
if (!lock.isHeldByCurrentThread())
throw new IllegalMonitorStateException();
if (newval == null)
throw new IllegalArgumentException();
if (!runState.get().isTransitionAllowed(newval)) {
throw new IllegalStateException("runState=" + runState
+ ", newValue=" + newval);
}
this.runState.set(newval);
}
/**
* The hash code is based on the {@link #getStartTimestamp()}.
*/
@Override
final public int hashCode() {
return hashCode;
}
/**
* True iff they are the same object or have the same start timestamp.
*
* @param o
* Another transaction object.
*/
final public boolean equals(final ITx o) {
return this == o || (o != null && startTime == o.getStartTimestamp());
}
@Override
final public long getStartTimestamp() {
return startTime;
}
// final public long getRevisionTimestamp() {
//
// if(readOnly) {
//
// throw new UnsupportedOperationException();
//
// }
//
// switch(runState) {
// case Active:
// case Aborted:
// throw new IllegalStateException();
// case Prepared:
// case Committed:
// /*
// * Note: A committed tx will have a zero revision time if it was
// * read-only or if it was read-write but did not write any data.
// */
// return revisionTime;
// }
//
// throw new AssertionError();
//
// }
/**
* Returns a string representation of the transaction start time.
*/
@Override
final public String toString() {
/*
* Note: Representation MUST NOT have dependencies on lock state!
*/
// return Long.toString(startTime);
return "LocalTxState{startTime=" + startTime + ",readsOnCommitTime="
+ readsOnCommitTime + ",runState=" + runState + "}";
}
/**
* {@inheritDoc}
*
* Note: The value is valid as of the instant that the run state is
* inspected. The caller must hold a lock if they want to act based on
* non-final run states.
*/
@Override
final public boolean isActive() {
// if(!lock.isHeldByCurrentThread())
// throw new IllegalMonitorStateException();
return runState.get() == RunState.Active;
}
/**
* {@inheritDoc}
*
* Note: The value is valid as of the instant that the run state is
* inspected. The caller must hold a lock if they want to act based on
* non-final run states.
*/
@Override
final public boolean isPrepared() {
// if(!lock.isHeldByCurrentThread())
// throw new IllegalMonitorStateException();
return runState.get() == RunState.Prepared;
}
/**
* {@inheritDoc}
*
* Note: The value is valid as of the instant that the run state is
* inspected. The caller must hold a lock if they want to act based on
* non-final run states.
*/
@Override
final public boolean isComplete() {
// if(!lock.isHeldByCurrentThread())
// throw new IllegalMonitorStateException();
final RunState runState = this.runState.get();
return runState == RunState.Committed || runState == RunState.Aborted;
}
/**
* {@inheritDoc}
*
* Note: The value is valid as of the instant that the run state is
* inspected. The caller must hold a lock if they want to act based on
* non-final run states.
*/
@Override
final public boolean isCommitted() {
// if(!lock.isHeldByCurrentThread())
// throw new IllegalMonitorStateException();
return runState.get() == RunState.Committed;
}
/**
* {@inheritDoc}
*
* Note: The value is valid as of the instant that the run state is
* inspected. The caller must hold a lock if they want to act based on
* non-final run states.
*/
@Override
final public boolean isAborted() {
// if(!lock.isHeldByCurrentThread())
// throw new IllegalMonitorStateException();
return runState.get() == RunState.Aborted;
}
// /**
// * Abort the transaction.
// *
// * @throws IllegalStateException
// * if the transaction is already complete.
// */
// public void abort() {
//
// if (!lock.isHeldByCurrentThread())
// throw new IllegalMonitorStateException();
//
// if (INFO)
// log.info("tx=" + this);
//
// try {
//
// setRunState(RunState.Aborted);
//
// localTransactionManager.deactivateTx(this);
//
// ResourceManager.closeTx(startTime, 0L/* commitTime */, true);
//
// } finally {
//
// releaseResources();
//
// }
//
// }
/**
* Validate the write set of the named indices isolated transaction and
* merge down that write set onto the corresponding unisolated indices but
* DOES NOT commit the data. The {@link RunState} is NOT changed by this
* method.
*
* For a single-phase commit the caller MUST hold an exclusive lock on the
* unisolated indices on which this operation will write.
*
* For a distributed transaction, the caller MUST hold a lock on the
* {@link WriteExecutorService} for each {@link IDataService} on which the
* transaction has written.
*
* @param revisionTime
* The revision time assigned by a centralized transaction
* manager service -or- ZERO (0L) IFF the transaction is
* read-only.
*
* @throws IllegalStateException
* if the transaction is not active.
* @throws ValidationError
* If the transaction can not be validated.
* @throws IllegalMonitorStateException
* unless the caller holds the {@link #lock}.
* @throws UnsupportedOperationException
* if the transaction is read-only.
*/
public void prepare(final long revisionTime) {
if(readOnly)
throw new UnsupportedOperationException();
if (!lock.isHeldByCurrentThread())
throw new IllegalMonitorStateException();
if (log.isInfoEnabled())
log.info("tx=" + this);
if (!isActive()) {
throw new IllegalStateException(NOT_ACTIVE);
}
if (!isEmptyWriteSet()) {
try {
/*
* Validate against the current state of the various indices on
* write the transaction has written.
*/
if (!validateWriteSets()) {
throw new ValidationError();
}
/*
* Merge each isolated index into the global scope. This also
* marks the tuples on which the transaction has written with
* the [revisionTime]. This operation MUST succeed (at a logical
* level) since we have already validated (neither read-write
* nor write-write conflicts exist).
*
* Note: This MUST be run as an AbstractTask which declares the
* unisolated indices so that has the appropriate locks on those
* indices when it executes. The AbstractTask will either
* succeed or fail. If it succeeds, then the tx will be made
* restart-safe at the group commit. If it fails or if the group
* commit fails, then the writes on the unisolated indices will
* be discarded.
*/
mergeOntoGlobalState(revisionTime);
} catch (ValidationError ex) {
throw ex;
}
}
}
// final public void mergeDown(final long revisionTime) {
//
// lock.lock();
//
// try {
//
// if(INFO)
// log.info("tx="+this);
//
// if (!isPrepared()) {
//
// if (!isComplete()) {
//
// abort();
//
// }
//
// throw new IllegalStateException(NOT_PREPARED);
//
// }
//
//// // The commitTime is zero unless this is a writable transaction.
//// final long commitTime = readOnly ? 0L : getCommitTimestamp();
//
// try {
//
// if (!readOnly && !isEmptyWriteSet()) {
//
// /*
// * Merge each isolated index into the global scope. This
// * also marks the tuples on which the transaction has
// * written with the [revisionTime]. This operation MUST
// * succeed (at a logical level) since we have already
// * validated (neither read-write nor write-write conflicts
// * exist).
// *
// * Note: This MUST be run as an AbstractTask which declares
// * the unisolated indices so that has the appropriate locks
// * on those indices when it executes. The AbstractTask will
// * either succeed or fail. If it succeeds, then the tx will
// * be made restart-safe at the group commit. If it fails or
// * if the group commit fails, then the writes on the
// * unisolated indices will be discarded.
// */
//
// mergeOntoGlobalState(revisionTime);
//
// // // Atomic commit.
// // journal.commitNow(commitTime);
//
// }
//
// runState = RunState.Committed;
//
// localTransactionManager.completedTx(this);
//
// ResourceManager.closeTx(startTime, revisionTime, false);
//
// } catch (Throwable t) {
//
// /*
// * Note: If the operation fails then we need to discard any
// * changes that have been merged down into the global state.
// * Failure to do this will result in those changes becoming
// * restart-safe when the next transaction commits. This is
// * easily done simply by (a) running this operation as an
// * AbstractTask; and (b) throwing an exception. The AbstractTask
// * will automatically discard its write set such that there will
// * be no side-effect on the persistent state of the unisolated
// * indices.
// *
// * Note: We do an abort() here just to set the appropriate
// * runState and other misc. handshaking.
// */
//
// abort();
//
// if (t instanceof RuntimeException)
// throw (RuntimeException) t;
//
// throw new RuntimeException(t);
//
// } finally {
//
// releaseResources();
//
// }
//
//// return revisionTime;
//
// } finally {
//
// lock.unlock();
//
// }
//
// }
/**
* This method must be invoked any time a transaction completes in order to
* release resources held by that transaction.
*/
protected void releaseResources() {
if(readOnly)
return;
assert lock.isHeldByCurrentThread();
if(!isComplete()) {
throw new IllegalStateException();
}
/*
* Release hard references to any named btrees isolated within this
* transaction so that the JVM may reclaim the space allocated to them
* on the heap.
*/
indices.clear();
/*
* Note: The BTree instances used to isolated the tx are not registered
* under a name on the temporary store. There is no concept of deleting
* an unregistered BTree -- not unless we change the temporary store to
* use a r/w backing store rather than a worm.
*/
// /*
// * Close and delete the TemporaryRawStore.
// *
// * Note: when changing to use a shared temporary store modify this to
// * drop the BTree for the isolated indices on the temporary store. That
// * will reduce clutter in its Name2Addr object.
// */
// if (tmpStore != null && tmpStore.isOpen()) {
//
// tmpStore.close();
//
// }
}
/**
* @todo This might need to be a full {@link Journal} using
* {@link BufferMode#Temporary} in order to have concurrency control
* for the isolated named indices. This would let us leverage the
* existing {@link WriteExecutorService} for handling concurrent
* operations within a transaction on the same named _isolated_
* resource. There are a lot of issues here, including the level of
* concurrency expected for transactions. Also, note that the write
* set of the tx is not restart safe, we never force writes to disk,
* etc. Those are good fits for the {@link BufferMode#Temporary}
* {@link BufferMode}. However, it might be nice to do without having
* a {@link WriteExecutorService} per transaction, e.g., by placing
* the named indices for a transaction within a namespace for that tx.
*
* @todo Rather than creating a distinct {@link TemporaryStore} for each tx
* and then closing and deleting the store when the tx completes, just
* use the temporary store factory. Once there are no more tx's using
* a given temporary store it will automatically be finalized and
* deleted. However, it is important that we namespace the indices so
* that different transactions do not see one another's data.
*
* We can do this just as easily with {@link BufferMode#Temporary},
* but {@link IIndexStore#getTempStore()} would have to be modified.
* However, that would give us more concurrency control in the tmp
* stores and we might need that for concurrent access to named
* indices (see above).
*/
private IRawStore getTemporaryStore() {
return resourceManager.getLiveJournal().getTempStore();
// assert lock.isHeldByCurrentThread();
//
// if (tmpStore == null) {
//
// final int offsetBits = resourceManager.getLiveJournal()
// .getOffsetBits();
//
// tmpStore = new TemporaryRawStore(offsetBits);
//
// }
//
// return tmpStore;
}
/**
* Invoked when a writable transaction prepares in order to validate its
* write sets (one per isolated index).
*
* @return true iff the write sets were validated.
*/
public boolean validateWriteSets() {
/*
* for all isolated btrees, if(!validate()) return false;
*/
final Iterator> itr = indices.entrySet()
.iterator();
while (itr.hasNext()) {
final Map.Entry entry = itr.next();
final String name = entry.getKey();
final IsolatedFusedView isolated = (IsolatedFusedView) entry.getValue();
/*
* Note: this is the live version of the named index. We need to
* validate against the live version of the index, not some
* historical state.
*/
final AbstractBTree[] sources = resourceManager.getIndexSources(
name, UNISOLATED);
if (sources == null) {
log.warn("Index does not exist: " + name);
return false;
}
if (!isolated.validate( sources )) {
// Validation failed.
if(log.isInfoEnabled())
log.info("validation failed: " + name);
return false;
}
}
return true;
}
/**
* {@link Callable} checkpoints an index.
*
* @author Bryan
* Thompson
*
* @see
* Flush indices in parallel during checkpoint to reduce IO latency
*/
private class CheckpointIndexTask implements Callable {
private final String name;
private final IsolatedFusedView isolated;
public CheckpointIndexTask(final String name,
final IsolatedFusedView isolated) {
if (name == null)
throw new IllegalArgumentException();
if (isolated == null)
throw new IllegalArgumentException();
this.name = name;
this.isolated = isolated;
}
@Override
public Void call() throws Exception {
if(log.isInfoEnabled())
log.info("Writing checkpoint: "+name);
try {
/*
* Note: this is the live version of the named index. We need to
* merge down onto the live version of the index, not onto some
* historical state.
*/
final AbstractBTree[] sources = resourceManager.getIndexSources(
name, UNISOLATED);
if (sources == null) {
/*
* Note: This should not happen since we just validated the
* index.
*/
throw new AssertionError();
}
/*
* Copy the validated write set for this index down onto the
* corresponding unisolated index, updating version counters, delete
* markers, and values as necessary in the unisolated index.
*/
isolated.mergeDown(revisionTime, sources);
/*
* Write a checkpoint so that everything is on the disk. This
* reduces both the latency for the commit and the possibilities for
* error.
*/
isolated.getWriteSet().writeCheckpoint();
} catch (Throwable t) {
// adds the name to the stack trace.
throw new RuntimeException("Could not commit index: name="
+ name, t);
}
// Done.
return null;
}
}
/**
* Invoked during commit processing to merge down the write set from each
* index isolated by this transactions onto the corresponding unisolated
* index on the database. This method invoked iff a transaction has
* successfully prepared and hence is known to have validated successfully.
* The default implementation is a NOP.
*
* @param revisionTime
*
* @see Flush indices in parallel during checkpoint to reduce IO
* latency
*/
protected void mergeOntoGlobalState(final long revisionTime) {
this.revisionTime = revisionTime;
// Create tasks to checkpoint the indices.
final List> tasks = new LinkedList>();
{
final Iterator> itr = indices
.entrySet().iterator();
while (itr.hasNext()) {
final Map.Entry entry = itr.next();
final String name = entry.getKey();
final IsolatedFusedView isolated = (IsolatedFusedView) entry
.getValue();
tasks.add(new CheckpointIndexTask(name, isolated));
}
}
/*
* Submit checkpoint tasks.
*
* Note: Method blocks until all tasks are done.
*/
final List> futures;
try {
futures = resourceManager.getLiveJournal().getExecutorService()
.invokeAll(tasks);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
/*
* Check Futures for errors.
*
* Note: Per above, all futures are known to be done.
*/
for (Future f : futures) {
try {
f.get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
}
/**
* Return a named index. The index will be isolated at the same level as
* this transaction. Changes on the index will be made restart-safe iff the
* transaction successfully commits.
*
* @param name
* The name of the index.
*
* @return The named index or null
if no index is registered
* under that name.
*
* @exception IllegalStateException
* if the transaction is not active.
*/
@Override
public ILocalBTreeView getIndex(final String name) {
if (name == null)
throw new IllegalArgumentException();
if(readOnly) {
/*
* Note: Access to the indices currently goes through the
* IResourceManager interface for a read-only transaction.
*/
throw new UnsupportedOperationException();
}
/*
* @todo lock could be per index for higher concurrency rather than for
* all indices which you might access through this tx.
*/
lock.lock();
try {
if (!isActive()) {
throw new IllegalStateException(NOT_ACTIVE);
}
/*
* Test the cache - this is used so that we can recover the same
* instance on each call within the same transaction.
*/
if (indices.containsKey(name)) {
// Already defined.
return indices.get(name);
}
final ILocalBTreeView index;
/*
* See if the index was registered as of the ground state used by
* this transaction to isolated indices.
*
* Note: IResourceManager#getIndex(String name,long timestamp) calls
* us when the timestamp identifies an active transaction so we MUST
* NOT call that method ourselves! Hence there is some replication
* of logic between that method and this one.
*/
final AbstractBTree[] sources = resourceManager.getIndexSources(
name, readsOnCommitTime);// startTime);
if (sources == null) {
/*
* The named index was not registered as of the transaction
* ground state.
*/
if (log.isInfoEnabled())
log.info("No such index: " + name + ", startTime="
+ startTime);
return null;
}
if (!sources[0].getIndexMetadata().isIsolatable()) {
throw new RuntimeException("Not isolatable: " + name);
}
/*
* Isolate the named btree.
*/
// if (readOnly) {
//
// assert sources[0].isReadOnly();
//
// if (sources.length == 1) {
//
// index = sources[0];
//
// } else {
//
// index = new FusedView(sources);
//
// }
//
// } else {
/*
* Setup the view. The write set is always the first element in
* the view.
*/
// the view definition.
final AbstractBTree[] b = new AbstractBTree[sources.length + 1];
/*
* Create the write set on a temporary store.
*
* Note: The BTree is NOT registered under a name so it can not
* be discovered on the temporary store. This is fine since we
* hold onto a hard reference to the BTree in [indices].
*/
b[0] = BTree.create(getTemporaryStore(), sources[0]
.getIndexMetadata().clone());
System.arraycopy(sources, 0, b, 1, sources.length);
// create view with isolated write set.
index = new IsolatedFusedView(-startTime, b);
// report event.
ResourceManager.isolateIndex(startTime, name);
// }
indices.put(name, index);
return index;
} finally {
lock.unlock();
}
}
@Override
final public boolean isEmptyWriteSet() {
if (readOnly)
return true;
lock.lock();
try {
final Iterator itr = indices.values().iterator();
while (itr.hasNext()) {
final IsolatedFusedView ndx = (IsolatedFusedView) itr.next();
if (!ndx.isEmptyWriteSet()) {
// At least one isolated index was written on.
return false;
}
}
return true;
} finally {
lock.unlock();
}
}
@Override
final public String[] getDirtyResource() {
if (readOnly)
return EMPTY_ARRAY;
lock.lock();
try {
return indices.keySet().toArray(new String[indices.size()]);
} finally {
lock.unlock();
}
}
private static final String[] EMPTY_ARRAY = new String[0];
}