/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
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 10, 2007
*/
package com.bigdata.journal;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.log4j.Logger;
import org.apache.log4j.MDC;
import com.bigdata.bfs.BigdataFileSystem;
import com.bigdata.bfs.GlobalFileSystemHelper;
import com.bigdata.btree.AbstractBTree;
import com.bigdata.btree.BTree;
import com.bigdata.btree.Checkpoint;
import com.bigdata.btree.ICheckpointProtocol;
import com.bigdata.btree.IDirtyListener;
import com.bigdata.btree.IIndex;
import com.bigdata.btree.ILocalBTreeView;
import com.bigdata.btree.IndexMetadata;
import com.bigdata.btree.view.FusedView;
import com.bigdata.concurrent.NonBlockingLockManager;
import com.bigdata.counters.CounterSet;
import com.bigdata.ha.HAGlue;
import com.bigdata.ha.QuorumService;
import com.bigdata.htree.AbstractHTree;
import com.bigdata.mdi.IResourceMetadata;
import com.bigdata.quorum.AsynchronousQuorumCloseException;
import com.bigdata.quorum.Quorum;
import com.bigdata.rawstore.IAllocationContext;
import com.bigdata.rawstore.IPSOutputStream;
import com.bigdata.relation.locator.DefaultResourceLocator;
import com.bigdata.relation.locator.ILocatableResource;
import com.bigdata.relation.locator.IResourceLocator;
import com.bigdata.resources.NoSuchStoreException;
import com.bigdata.resources.ResourceManager;
import com.bigdata.resources.StaleLocatorException;
import com.bigdata.resources.StaleLocatorReason;
import com.bigdata.resources.StoreManager.ManagedJournal;
import com.bigdata.rwstore.IRWStrategy;
import com.bigdata.rwstore.IRawTx;
import com.bigdata.sparse.GlobalRowStoreHelper;
import com.bigdata.sparse.SparseRowStore;
import com.bigdata.util.InnerCause;
import com.bigdata.util.concurrent.TaskCounters;
import cutthecrap.utils.striterators.Filter;
import cutthecrap.utils.striterators.Resolver;
import cutthecrap.utils.striterators.Striterator;
/**
* Abstract base class for tasks that may be submitted to the
* {@link ConcurrencyManager}. Tasks may be isolated (by a transaction),
* unisolated, read-committed, or historical reads. Tasks access named resources
* (aka indices), which they pre-declare in their constructors.
* <p>
* A read-committed task runs against the most recently committed view of the
* named index. A historical read task runs against a historical view of the
* named index, but without guarantees of transactional isolation. Concurrent
* readers are permitted without locking on the same index.
* <p>
* An unisolated task reads and writes on the "live" index. Note that only a
* single thread may write on a {@link BTree} at a time. Therefore unisolated
* tasks (often referred to as writers) obtain an exclusive lock on the named
* index(s). When more than one named index is used, the locks are used to infer
* a partial ordering of the writers allowing as much concurrency as possible.
* Pre-declaration of locks allows us to avoid deadlocks in the lock system.
* <p>
* Isolated tasks are part of a larger transaction. Transactions are started and
* committed using an {@link ITransactionManagerService}. Transactional tasks
* run with full concurrency using an MVCC (Multi-Version Concurrency Control)
* strategy. When a transaction is committed (by the
* {@link ITransactionManagerService}) it must wait for lock(s) on the
* unisolated named indices on which it has written before it may validate and
* commit.
* <p>
* Note: You MUST submit a distinct instance of this task each time you
* {@link ConcurrencyManager#submit(AbstractTask)} it.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
*/
public abstract class AbstractTask<T> implements Callable<T>, ITask<T> {
static protected final Logger log = Logger.getLogger(AbstractTask.class);
/**
* Used to protect against re-submission of the same task object.
*/
private final AtomicBoolean submitted = new AtomicBoolean(false);
/**
* The object used to manage exclusive access to the unisolated indices.
*/
protected final ConcurrencyManager concurrencyManager;
/**
* The object used to manage local transactions.
*/
protected final AbstractLocalTransactionManager transactionManager;
/**
* The object used to manage access to the resources from which views of the
* indices are created.
*/
protected final IResourceManager resourceManager;
/**
* Optionally verifies that the locks are held before/after the task
* executes.
*
* @todo The most common reason to see an exception when this is enabled
* that you did not submit the task to the concurrency manager and
* hence it was not processed by the lock manager. E.g., you invoked
* call() directly on some subclass of AbstractTask. That is a no-no,
* unless you are using the Journal as a personal (no concurrency)
* store. In that case it is Ok and some of the unit tests are written
* that way which is why this is turned off by default. In particular,
* it tends to interfere with some of the tx test suites since they
* were written before the concurrency API was added.
*/
private static final boolean verifyLocks = false;
/**
* The object used to manage access to the resources from which views of the
* indices are created.
*/
@Override
public final IResourceManager getResourceManager() {
return resourceManager;
}
@Override
synchronized public final IJournal getJournal() {
if (journal == null) {
journal = resourceManager.getJournal(timestamp);
if (journal == null) {
log.warn("No such journal: timestamp=" + timestamp);
return null;
}
if (timestamp == ITx.UNISOLATED) {
journal = new IsolatedActionJournal((AbstractJournal) journal);
} else if (readOnly) {
// disallow writes.
journal = new ReadOnlyJournal((AbstractJournal) journal);
}
}
return journal;
}
/** Guarded by <code>synchronized(AbstractTask)</code>. */
private IJournal journal;
/**
* The transaction identifier -or- {@link ITx#UNISOLATED} if the operation
* is NOT isolated by a transaction, -or- {@link ITx#READ_COMMITTED}, -or-
* <code>timestamp</code> to read from the most recent commit point not
* later than <i>timestamp</i>.
*/
protected final long timestamp;
/**
* The timestamp of the group commit for an {@link ITx#UNISOLATED} task
* which executes successfully and then iff the group commit succeeds.
* Otherwise ZERO (0L).
*/
long commitTime = 0L;
/**
* The timestamp of the group commit for an {@link ITx#UNISOLATED} task
* which executes successfully and then iff the group commit succeeds.
* Otherwise ZERO (0L).
*/
public long getCommitTime() {
return commitTime;
}
/**
* True iff the operation is isolated by a transaction.
*/
protected final boolean isReadWriteTx;
/**
* True iff the operation is not permitted to write.
*/
protected final boolean readOnly;
/**
* The name of the resource(s) on which the task will operation (read,
* write, add, drop, etc). This is typically the name of one or more
* indices.
* <p>
* Note: When the operation is an unisolated writer an exclusive lock MUST
* be obtain on the named resources(s) before the operation may execute.
* <p>
* Note: this is private so that people can not mess with the resource names
* in {@link #doTask()}.
*/
private final String[] resource;
/**
* The transaction object iff the operation is isolated by a transaction
* and otherwise <code>null</code>.
*/
protected final Tx tx;
/**
* Cache of named indices resolved by this task for its {@link #timestamp}.
*
* @see #getIndex(String name)
* @see #getIndexLocal(String)
*/
final private Map<String,ILocalBTreeView> indexCache;
/**
* Read-only copy of a {@link Name2Addr.Entry} with additional flags to
* track whether an index has been registered or dropped by the task.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
*
* @todo are these bits (combined with the replacement of the {@link Entry}
* in {@link #n2a}) sufficiently flexible to allow a task to do a
* drop/add sequence, or a more general {add,drop}+ sequence.
*/
static private class Entry extends Name2Addr.Entry {
/** <code>true</code> iff the index is registered by the task. */
boolean registeredIndex = false;
/** <code>true</code> iff the index is dropped by the task. */
boolean droppedIndex = false;
Entry(final Name2Addr.Entry entry) {
super(entry.name, entry.checkpointAddr, entry.commitTime);
}
/**
* Ctor used when registering an index.
* @param name
* @param checkpointAddr
* @param commitTime
*/
Entry(final String name, final long checkpointAddr, final long commitTime) {
super(name, checkpointAddr, commitTime);
registeredIndex = true;
}
}
/**
* A map containing the {@link Entry}s for resources declared by
* {@link ITx#UNISOLATED} tasks. The map is populated atomically by
* {@link #setupIndices()} before the user task begins to execute.
* <p>
* There are several special cases designed to handle add and drop of named
* indices in combination with transient flags on the {@link Entry}:
* <ol>
*
* <li>If an resource was declared by the task and there was no such index
* in existence when the task began to execute, then there will NOT be an
* {@link Entry} for the resource in this map.</li>
*
* <li>If an existing index was dropped by the task, then then the resource
* will be associated with a {@link Entry} whose [droppedIndex] flag is set
* but the index will NOT be on the {@link #commitList}. When the task
* checkpoints the indices, that [dropppedIndex] flag will be used to drop
* the named index from the {@link ITx#UNISOLATED} {@link Name2Addr} object
* (and it is an error if the named index does not exist in
* {@link Name2Addr} at that time).</li>
*
* <li>If an index is registered by a task, then either there MUST NOT be
* an entry in {@link #n2a} as of the time that the task registers the index
* -or- the entry MUST have the [droppedIndex] flag set. </li>
*
* </ol>
*/
private Map<String, Entry> n2a;
/**
* The commit list contains metadata for all indices that were made dirty by
* an {@link ITx#UNISOLATED} task.
*/
private ConcurrentHashMap<String, DirtyListener> commitList;
/**
* Atomic read of {@link Entry}s for declares resources into {@link #n2a}.
* This is done before an {@link ITx#UNISOLATED} task executes. If the task
* executes successfully then changes to {@link #n2a} are applied to the
* unisolated {@link Name2Addr} object when the task completes. If the task
* fails, then {@link #n2a} is simply discarded (since we have not written
* on the unisolated {@link Name2Addr} we do not need to rollback any
* changes).
*/
private void setupIndices() {
if (n2a != null)
throw new IllegalStateException();
n2a = new HashMap<String, Entry>(resource.length);
/*
* Note: getIndex() sets the listener on the BTree. That listener is
* responsible for putting dirty indices onto the commit list.
*/
commitList = new ConcurrentHashMap<String,DirtyListener>(resource.length);
// the unisolated name2Addr object.
final Name2Addr name2Addr = resourceManager.getLiveJournal()._getName2Addr();
if (name2Addr == null) {
/*
* Note: I have seen name2Addr be [null] here on a system with too
* many files open which caused a cascade of failures. I added this
* thrown exception so that we could have a referent if the problem
* shows up again.
*/
throw new AssertionError("Name2Addr not loaded? : "
+ resourceManager.getLiveJournal());
}
/*
* Copy entries to provide an isolated view of name2Addr as of
* the time that this task begins to execute.
*/
synchronized(name2Addr) {
for(String s : resource) {
final Name2Addr.Entry tmp = name2Addr.getEntry(s);
if (tmp != null) {
/*
* Exact match on a named index.
*
* Add a read-only copy of the entry with additional state
* for tracking registration and dropping of named indices.
*
* Note: We do NOT fetch the indices here, just copy their
* last checkpoint metadata from Name2Addr.
*/
n2a.put(s, new Entry(tmp));
} else {
/**
* Add a read-only copy of the Name2Addr entry for all
* entries spanned by that namespace. This provides the
* additional state for tracking registration and dropping
* of named indices and also supports hierarchical locking
* pattersn.
*
* Note: We do NOT fetch the indices here, just copy their
* last checkpoint metadata from Name2Addr.
*
* @see <a
* href="http://trac.blazegraph.com/ticket/566"
* > Concurrent unisolated operations against multiple
* KBs </a>
*/
final Iterator<String> itr = Name2Addr.indexNameScan(s,
name2Addr);
while (itr.hasNext()) {
final String t = itr.next();
final Name2Addr.Entry tmp2 = name2Addr.getEntry(t);
n2a.put(t, new Entry(tmp2));
}
}
}
}
}
/**
* An instance of this {@link DirtyListener} is registered with each
* {@link ITx#UNISOLATED} named index that we administer to listen for
* events indicating that the index is dirty. When we get that event we
* stick the {@link DirtyListener} on the {@link #commitList}. This makes
* the commit protocol simpler since the {@link DirtyListener} has both the
* name of the index and the reference to the index and we need both on hand
* to do the commit.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
*/
private class DirtyListener implements IDirtyListener {
private final String name;
private final ICheckpointProtocol ndx;
@Override
public String toString() {
return "DirtyListener{name="+name+"}";
//return "DirtyListener{name="+name+", btree="+btree.getCheckpoint()+"}";
}
DirtyListener(final String name, final ICheckpointProtocol ndx) {
assert name!=null;
assert ndx!=null;
this.name = name;
this.ndx = ndx;
}
/**
* Add <i>this</i> to the {@link AbstractTask#commitList}.
*
* @param btree
* The index reporting that it is dirty.
*/
@Override
public void dirtyEvent(final ICheckpointProtocol btree) {
assert btree == this.ndx;
if (commitList.put(name, this) != null) {
if (log.isInfoEnabled())
log.info("Added index to commit list: name=" + name);
}
}
}
/**
* Release hard references to named indices to facilitate GC.
*/
private void clearIndexCache() {
if (log.isInfoEnabled())
log.info("Clearing hard reference cache: " + indexCache.size()
+ " indices accessed");
// final Iterator<Map.Entry<String, ILocalBTreeView>> itr = indexCache
// .entrySet().iterator();
//
// while (itr.hasNext()) {
//
// final Map.Entry<String, ILocalBTreeView> entry = itr.next();
//
// final String name = entry.getKey();
//
// final ILocalBTreeView ndx = entry.getValue();
//
// resourceManager.addIndexCounters(name, timestamp, ndx);
//
// }
indexCache.clear();
if (commitList != null) {
/*
* Clear the commit list so that we do not hold hard references.
*
* Note: it is important to do this here since this code will be
* executed even if the task fails so the commit list will always be
* cleared and we will not hold hard references to indices accessed
* by the task.
*/
commitList.clear();
}
}
/**
* {@inheritDoc}
* <p>
* Note: There are two ways in which a task may access an
* {@link ITx#UNISOLATED} index, but in all cases access to the index is
* delegated to this method. First, the task can use this method directly.
* Second, the task can use {@link #getJournal()} and then use
* {@link IJournal#getIndex(String)} on that journal, which is simply
* delegated to this method. See {@link IsolatedActionJournal}.
*
* @see http://trac.blazegraph.com/ticket/585 (GIST)
*/
@Override
synchronized final public ILocalBTreeView getIndex(final String name) {
if (name == null)
throw new NullPointerException();
// validate that this is a declared index.
assertResource(name);
// verify still running.
assertRunning();
/*
* Test the named index cache first.
*/
{
final ILocalBTreeView index = indexCache.get(name);
if (index != null) {
// Cached value.
return index;
}
}
if (timestamp == ITx.UNISOLATED) {
final StaleLocatorReason reason = resourceManager.getIndexPartitionGone(name);
if (reason != null) {
throw new StaleLocatorException(name, reason);
}
// entry from isolated view of Name2Addr as of task startup (call()).
final Entry entry = n2a.get(name);
if (entry == null) {
// index did not exist at that time.
throw new NoSuchIndexException(name);
}
long checkpointAddr = entry.checkpointAddr;
/*
* Note: At this point we have an exclusive lock on the named
* unisolated index. We recover the unisolated index from
* Name2Addr's cache. If not found, then we load the unisolated
* index from the store, set the [lastCommitTime], and enter it into
* the unisolated Name2Addr's cache of unisolated indices.
*/
ICheckpointProtocol ndx;
// the unisolated name2Addr object.
final Name2Addr name2Addr = resourceManager.getLiveJournal()
._getName2Addr();
synchronized (name2Addr) {
/*
* RWStore: There are two reasons why we must use shadow
* allocations for unisolated operations against the RWStore.
*
* (1) A rollback of an unisolated operation which caused
* mutations to the structure of an index will cause operations
* which access the index the rollback to fail since the backing
* allocations would have been immediately recycled by the
* RWStore (if running with a zero retention policy).
*
* (2) Allocations made during the unisolated operation should
* be immediately recycled if the operation is rolled back. This
* will not occur unless the unisolated operation makes those
* allocations against a shadow allocation context. Given that
* it does so, the rollback logic must also discard the shadow
* allocator in order for the shadowed allocations to be
* reclaimed immediately.
*
* In order to use shadow allocations, the unisolated index MUST
* be loaded using the IsolatedActionJournal. There are two
* places immediate below where it tests the cache and where it
* loads using the AbstractJournal, both of which are not
* appropriate as they fail to impose the IsolatedActionJournal
* with the consequence that the allocation contexts are not
* isolated. [Also, we do not want N2A to cache references to a
* B+Tree backed by a different shadow journal.]
*/
if ((resourceManager.getLiveJournal().getBufferStrategy() instanceof IRWStrategy)) {
/*
* Note: Do NOT use the name2Addr cache for the RWStore.
* Each unisolated index view MUST be backed by a shadow
* journal!
*
* But, fetch the btree from the cache to ensure we use the
* most recent checkpoint
*/
ndx = null;
final ICheckpointProtocol tmp_ndx = name2Addr
.getIndexCache(name);
if (tmp_ndx != null) {
checkpointAddr = tmp_ndx.getCheckpoint()
.getCheckpointAddr();
}
} else {
// Recover from unisolated index cache.
ndx = name2Addr.getIndexCache(name);
}
if (ndx == null) {
// wrap with the IsolatedActionJournal.
final IJournal tmp = getJournal();
// tmp = resourceManager.getLiveJournal();
// re-load btree from the store.
ndx = Checkpoint.loadFromCheckpoint(//
tmp, // backing store.
checkpointAddr,//
false// readOnly
);
// set the lastCommitTime on the index.
ndx.setLastCommitTime(entry.commitTime);
// add to the unisolated index cache (must not exist).
name2Addr.putIndexCache(name, ndx, false/* replace */);
// set performance counters iff the class supports it.
if (ndx instanceof AbstractBTree) {
((AbstractBTree) ndx).setBTreeCounters(resourceManager
.getIndexCounters(name));
} else if (ndx instanceof AbstractHTree) {
((AbstractHTree) ndx).setBTreeCounters(resourceManager
.getIndexCounters(name));
}
}
}
try {
// wrap B+Tree as FusedView: FIXME GIST : BTree specific code path.
return getUnisolatedIndexView(name, (BTree) ndx);
} catch (NoSuchStoreException ex) {
/*
* Add a little more information to the stack trace.
*/
throw new NoSuchStoreException(entry.toString() + ":"
+ ex.getMessage(), ex);
}
} else {
final ILocalBTreeView tmp = resourceManager.getIndex(name, timestamp);
if (tmp == null) {
// Presume client has made a bad request
throw new NoSuchIndexException(name + ", timestamp="
+ timestamp);
// return null; // @todo return null to conform with Journal#getIndex(name)?
}
/*
* Put the index into a hard reference cache under its name so that
* we can hold onto it for the duration of the operation.
*/
indexCache.put(name, tmp);
return tmp;
}
}
/**
* Given the name of an index and a {@link BTree}, obtain the view for all
* source(s) described by the {@link BTree}s index partition metadata (if
* any), inserts that view into the {@link #indexCache}, and return the
* view.
* <p>
* Note: This method is used both when registering a new index (
* {@link #registerIndex(String, BTree)}) and when reading an index view
* from the source ({@link #getIndex(String)}).
*
* @param name
* The index name.
* @param btree
* The {@link BTree}.
*
* @return The view.
*/
private ILocalBTreeView getUnisolatedIndexView(final String name,
final BTree btree) {
// setup the task as the listener for dirty notices.
btree.setDirtyListener(new DirtyListener(name, btree));
// find all sources if a partitioned index.
final AbstractBTree[] sources = resourceManager.getIndexSources(name,
ITx.UNISOLATED, btree);
assert !sources[0].isReadOnly();
final ILocalBTreeView view;
if (sources.length == 1) {
view = (BTree) sources[0];
} else {
view = new FusedView(sources);
}
// put the index in our hard reference cache.
indexCache.put(name, view);
return view;
}
/**
* Registers an index
*
* @param name
* The index name.
* @param btree
* The {@link BTree} that will absorb writes for the index.
*
* @return The index on which writes may be made. Note that if the
* {@link BTree} describes an index partition with multiple sources
* then the returned object is a {@link FusedView} for that index
* partition as would be returned by
* {@link IResourceManager#getIndex(String, long)}.
*
* @throws UnsupportedOperationException
* unless the task is {@link ITx#UNISOLATED}
* @throws IndexExistsException
* if the index was already registered as of the time that this
* task began to execute.
*
* @todo should allow add/drop of indices within fully isolated read-write
* transactions as well.
*
* @see IBTreeManager#registerIndex(String, BTree)
*/
synchronized public IIndex registerIndex(final String name, final BTree btree) {
if (name == null)
throw new IllegalArgumentException();
if (btree == null)
throw new IllegalArgumentException();
// task must be unisolated.
assertUnisolated();
// must be a declared resource.
assertResource(name);
// verify still running.
assertRunning();
if(n2a.containsKey(name)) {
final Entry entry = n2a.get(name);
if(!entry.droppedIndex) {
throw new IndexExistsException(name);
}
// FALL THROUGH IFF INDEX WAS DROPPED BY THE TASK.
}
/*
* Note: logic is parallel to that in Name2Addr.
*/
// flush btree to the store to get the checkpoint record address.
final long checkpointAddr = btree.writeCheckpoint();
/*
* Create new Entry.
*
* Note: Entry has [registeredIndex := true].
*
* Note: The commit time here is a placeholder. It will be replaced with
* the actual commit time if the entry is propagated to Name2Addr and
* Name2Addr does a commit.
*/
final Entry entry = new Entry(name, checkpointAddr, 0L/* commitTime */);
/*
* Add entry to our isolated view (can replace an old [droppedIndex]
* entry).
*/
n2a.put(name, entry);
/*
* Make sure that the BTree is associated with the correct performance
* counters.
*/
btree.setBTreeCounters(resourceManager.getIndexCounters(name));
/*
* Note: delegate logic to materialize the view in case BTree is an
* index partition with more than one source. This will also get the
* index view into [indexCache].
*/
final IIndex view = getUnisolatedIndexView(name, btree);
/*
* Verify that the caller's [btree] is part of the returned view.
*/
if(view instanceof AbstractBTree) {
assert btree == view;
} else {
assert btree == ((FusedView)view).getSources()[0];
}
/*
* Fire the listener which will get the btree onto the commit list.
*
* Note: We MUST put the newly registered BTree on the commit list even
* if nothing else gets written on that BTree.
*/
((DirtyListener)btree.getDirtyListener()).dirtyEvent(btree);
return view;
}
/**
* Drops the named index.
*
* @param name
* The name of the index.
*
* @throws IllegalArgumentException
* if <i>name</i> is <code>null</code>.
* @throws UnsupportedOperationException
* unless the task is {@link ITx#UNISOLATED}
* @throws NoSuchIndexException
* if the named index is not registered as of the time that this
* task began to execute.
*
* @see IIndexManager#dropIndex(String)
*/
synchronized public void dropIndex(final String name) {
if (name == null)
throw new IllegalArgumentException();
// task must be unisolated.
assertUnisolated();
// must be a declared resource.
assertResource(name);
// verify still running.
assertRunning();
if(!n2a.containsKey(name)) {
throw new NoSuchIndexException(name);
}
// The entry for that index in our isolated view of name2Addr.
final Entry entry = n2a.get(name);
if(entry.droppedIndex) {
// its already been dropped by the task.
throw new NoSuchIndexException(name);
}
entry.droppedIndex = true;
// clear from the index cache.
indexCache.remove(name);
// clear from the commit list.
commitList.remove(name);
/*
* Note: Since the dropped indices are NOT on the commit list, when the
* task is checkpointed we MUST scan n2a and then drop any dropped
* indices without regard to whether they are on the commitList.
*/
}
/**
* {@link Callable} checkpoints an index.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan
* Thompson</a>
*
* @see <a href="http://trac.blazegraph.com/ticket/675" >
* Flush indices in parallel during checkpoint to reduce IO latency</a>
*/
private class CheckpointIndexTask implements Callable<Void> {
private final DirtyListener l;
public CheckpointIndexTask(final DirtyListener l) {
this.l = l;
}
@Override
public Void call() throws Exception {
if(log.isInfoEnabled())
log.info("Writing checkpoint: "+l.name);
try {
l.ndx.writeCheckpoint();
} catch (Throwable t) {
// adds the name to the stack trace.
throw new RuntimeException("Could not commit index: name="
+ l.name, t);
}
// Done.
return null;
}
@Override
public String toString() {
return getClass().getName() + "{name=" + l.name + "}";
}
}
/**
* Flushes any writes on unisolated indices touched by the task (those found
* on the {@link #commitList}) and reconciles {@link #n2a} (our isolated
* view of {@link Name2Addr}) with the {@link ITx#UNISOLATED}
* {@link Name2Addr} object.
* <p>
* This method is invoked after an {@link ITx#UNISOLATED} task has
* successfully completed its work, but while the task still has its locks.
*
* @return The elapsed time in nanoseconds for this operation.
*
* @see <a href="http://trac.blazegraph.com/ticket/675"
* >Flush indices in parallel during checkpoint to reduce IO
* latency</a>
*/
private long checkpointTask() {
assertUnisolated();
assertRunning();
/*
* Checkpoint the dirty indices.
*/
final int ndirty = commitList.size();
final long begin = System.nanoTime();
if (log.isInfoEnabled()) {
log.info("There are " + ndirty + " dirty indices "
+ commitList.keySet() + " : " + this);
}
/*
* Create task to checkpoint each entry in the snapshot of the commit
* list.
*/
final List<CheckpointIndexTask> tasks = new LinkedList<CheckpointIndexTask>();
for (final DirtyListener l : commitList.values()) {
assert indexCache.containsKey(l.name) : "Index not in cache? name="
+ l.name;
tasks.add(new CheckpointIndexTask(l));
}
/*
* Submit checkpoint tasks in parallel.
*
* This is done for each entry in the snapshot of the commit list.
*
* Note: This relies on getResourceManager() providing access to the
* IIndexManager interface.
*/
final List<Future<Void>> futures;
try {
final ExecutorService executorService = resourceManager
.getLiveJournal().getExecutorService();
/*
* Invoke tasks.
*
* Note: Blocks until all tasks are done. Hence we do NOT have to
* cancel these Futures. If we obtain them, then they are already
* done.
*/
futures = executorService.invokeAll(tasks);
} catch (InterruptedException e) {
// Interrupted while awaiting checkpoint(s).
throw new RuntimeException(e);
}
/*
* Check Futures.
*
* Note: Per above, the Futures are done. We are just checking them for
* errors.
*/
for (Future<Void> f : futures) {
try {
f.get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
if(log.isInfoEnabled()) {
final long elapsed = System.nanoTime() - begin;
log.info("Flushed " + ndirty + " indices in "
+ TimeUnit.NANOSECONDS.toMillis(elapsed) + "ms");
}
/*
* Atomically apply changes to Name2Addr. When the next groupCommit
* rolls around it will cause name2addr to run its commit protocol
* (which will be a NOP since it SHOULD NOT be a dirty listener when
* using the concurrency control) and then checkpoint itself, writing
* the updated Entry records as part of its own index state. Finally the
* AbstractJournal will record the checkpoint address of Name2Addr as
* part of the commit record, etc. Until then, the changes written on
* Name2Addr here will become visible as soon as we leave the
* synchronized(name2addr) block.
*
* Note: I've chosen to apply the changes to Name2Addr after the indices
* have been checkpointed. This means that you will do all the IO for
* the index checkpoints before you learn whether or not there is a
* conflict with an add/drop for an index. However, the alternative is
* to remain synchronized on [name2addr] during the index checkpoint,
* which is overly constraining on concurrency. (If you validate first
* but do not remained synchronized on name2addr then the validation can
* be invalidated by concurrent tasks completing.)
*
* However, unisolated tasks obtain exclusive locks for resources, so it
* SHOULD NOT be possible to fail validation here. If validation does
* fail (if the add/drop changes can not be propagated to name2addr)
* then either the logic for add/drop using our isolated [n2a] is
* busted, someone has gone around the concurrency control mechanisms to
* access name2addr directly, or there is someplace where access to
* name2addr is not synchronized.
*/
// the unisolated name2Addr object.
final Name2Addr name2Addr = resourceManager.getLiveJournal()._getName2Addr();
synchronized(name2Addr) {
for( final Entry entry : n2a.values() ) {
if(entry.droppedIndex) {
if (log.isInfoEnabled())
log.info("Dropping index on Name2Addr: " + entry.name);
name2Addr.dropIndex(entry.name);
} else if(entry.registeredIndex) {
if (log.isInfoEnabled())
log.info("Registering index on Name2Addr: "
+ entry.name);
name2Addr.registerIndex(entry.name, (BTree)commitList
.get(entry.name).ndx);
} else {
final DirtyListener l = commitList.get(entry.name);
if (l != null) {
/*
* Force the BTree onto name2addr's commit list.
*
* Note: This will also change the dirty listener to be
* [Name2Addr].
*
* Note: We checkpointed the index above. Therefore we
* mark it here as NOT needing to be checkpointed by
* Name2Addr. This flag is very important. Name2Addr
* DOES NOT acquire an exclusive lock on the unisolated
* index before attempting to checkpoint the index. The
* flag is set [false] here indicating to Name2Addr that
* it will write the current checkpoint record address
* on its next commit rather than attempting to
* checkpoint the index itself, which could lead to a
* concurrent modification problem.
*/
if (log.isInfoEnabled())
log.info("Transferring to Name2Addr commitList: "
+ entry.name);
name2Addr
.putOnCommitList(l.name, l.ndx, false/*needsCheckpoint*/);
}
}
}
}
/*
* Do clean up.
*/
// clear n2a.
n2a.clear();
// clear the commit list.
commitList.clear();
// Detach the allocation context used by the operation, but increment
// txCount
((IsolatedActionJournal) getJournal()).prepareCommit();
final long elapsed = System.nanoTime() - begin;
if (log.isInfoEnabled()) {
log.info("End task checkpoint after "
+ TimeUnit.NANOSECONDS.toMillis(elapsed) + "ms");
}
return elapsed;
}
/**
* Discard any allocations for writes on unisolated indices touched by the
* task for an {@link ITx#UNISOLATED} which fails, but while the task still
* has its locks.
*/
private void abortTask() {
((IsolatedActionJournal) getJournal()).abortContext();
}
/**
* Hook invoked by group commit on success/failure. It is invoked from
* within the group commit for each task which joins the commit group along
* either the code path where the commit succeeds or the code path where it
* fails. The boolean argument indicates whether or not the group commit
* succeeded. Throws exceptions are trapped and logged.
*/
void afterTaskHook(final boolean abort) {
((IsolatedActionJournal) getJournal()).completeTask();
}
/*
* End isolation support for name2addr.
*/
/**
* Flag is cleared if the task is aborted. This is used to refuse
* access to resources for tasks that ignore interrupts.
*/
volatile boolean aborted = false;
/**
* The {@link AbstractTask} increments various counters of interest to the
* {@link ConcurrencyManager} using this object.
*/
protected TaskCounters taskCounters;
@Override
public TaskCounters getTaskCounters() {
return taskCounters;
}
/*
* Timing data for this task.
*/
/**
* The time at which this task was submitted to the {@link ConcurrencyManager}.
*/
public long nanoTime_submitTask;
/**
* The time at which this task was assigned to a worker thread for
* execution.
*/
public long nanoTime_assignedWorker;
/**
* The time at which this task began to do its work. If the task needs to
* acquire exclusive resource locks, then this timestamp is set once those
* locks have been acquired. Otherwise this timestamp will be very close to
* the {@link #nanoTime_assignedWorker}.
*/
public long nanoTime_beginWork;
/**
* The time at which this task finished its work. Tasks with write sets must
* still do abort processing or await the next commit group.
*/
public long nanoTime_finishedWork;
/**
* The elapsed time in nanoseconds for a write task to checkpoint its
* index(s).
*/
public long checkpointNanoTime;
/**
* Convenience constructor variant for one named resource.
*
* @param concurrencyControl
* The object used to control access to the local resources.
* @param timestamp
* The transaction identifier -or- {@link ITx#UNISOLATED} IFF the
* operation is NOT isolated by a transaction -or-
* <code> - timestamp </code> to read from the most recent commit
* point not later than the absolute value of <i>timestamp</i> (a
* historical read).
* @param resource
* The resource on which the task will operate. E.g., the names
* of the index. When the task is an unisolated write task an
* exclusive lock will be requested on the named resource and the
* task will NOT run until it has obtained that lock.
* <p>
* The name may identify either a namespace or a concrete index
* object. If a concrete index object is discovered, only that
* index is isolated. Otherwise all indices having the same
* prefix as the namespace are isolated.
*/
protected AbstractTask(final IConcurrencyManager concurrencyManager,
final long timestamp, final String resource) {
this(concurrencyManager, timestamp, new String[] { resource });
}
/**
*
* @param concurrencyControl
* The object used to control access to the local resources.
* @param timestamp
* The transaction identifier, {@link ITx#UNISOLATED} for an
* unisolated view, {@link ITx#READ_COMMITTED} for a view as of
* the most recent commit point, or <code>timestamp</code> to
* read from the most recent commit point not later than that
* timestamp.
* @param resource
* The resource(s) on which the task will operate. E.g., the
* names of the index(s). When the task is an unisolated write
* task an exclusive lock will be requested on each named
* resource and the task will NOT run until it has obtained those
* lock(s).
* <p>
* The name may identify either a namespace or a concrete index
* object. If a concrete index object is discovered, only that
* index is isolated. Otherwise all indices having the same
* prefix as the namespace are isolated.
*/
protected AbstractTask(final IConcurrencyManager concurrencyManager,
final long timestamp, final String[] resource) {
if (concurrencyManager == null) {
throw new NullPointerException();
}
if (resource == null) {
throw new NullPointerException();
}
for (int i = 0; i < resource.length; i++) {
if (resource[i] == null) {
throw new NullPointerException();
}
}
// make sure we have the real ConcurrencyManager for addCounters()
this.concurrencyManager = (ConcurrencyManager) (concurrencyManager instanceof Journal ? ((Journal) concurrencyManager)
.getConcurrencyManager()
: concurrencyManager);
this.transactionManager = (AbstractLocalTransactionManager) concurrencyManager
.getTransactionManager();
this.resourceManager = concurrencyManager.getResourceManager();
this.timestamp = timestamp;
/*
* Note: A read-lock should be asserted for abs(timestamp) for the
* duration of the task. This can be accomplished either by requesting
* the necessary index views or by explicit handshaking with the
* ResourceManager (no read locks are required for simple journals).
*
* [This is currently handled by the weak value cache for clean indices
* and the commit list for dirty indices].
*/
this.resource = resource;
this.indexCache = new HashMap<String,ILocalBTreeView>(resource.length);
this.isReadWriteTx = TimestampUtility.isReadWriteTx(timestamp);
this.readOnly = TimestampUtility.isReadOnly(timestamp);
if (isReadWriteTx) {
if (resourceManager instanceof ResourceManager) {
/*
* This is a read-write transaction with a distributed database.
*/
// UUID of the dataService on which the tx will run.
final UUID dataServiceUUID = ((ResourceManager) resourceManager)
.getDataServiceUUID();
try {
/*
* Notify the transaction service. We send both the UUID of
* the dataService and the array of the named resources
* which the operation isolated by that transaction has
* requested. Transaction commits are placed into a partial
* order to avoid deadlocks where that ordered is determined
* by sorting the resources declared by the tx throughout its
* life cycle and the obtaining locks on all of those
* resources (in the distributed transaction service) before
* the commit may start. This is very similar to how we
* avoid deadlocks for unisolated operations running on a
* single journal or data service.
*
* Note: Throws IllegalStateException if [timestamp] does
* not identify an active transaction.
*
* FIXME In order to permit concurrent operations by the
* same transaction it MUST establish locks on the isolated
* resources. Those resources MUST be locatable (they will
* exist on a temporary store choosen by the tx when it first
* started on this data service). See IsolatedActionJournal's
* resource locator delegation pattern.
*/
final IDistributedTransactionService txs = (IDistributedTransactionService) transactionManager
.getTransactionService();
txs.declareResources(timestamp, dataServiceUUID, resource);
} catch (IOException e) {
// RMI problem.
throw new RuntimeException(e);
}
if (transactionManager.getTx(timestamp) == null) {
/**
* Start tx on this data service.
*
* FIXME This should be passing the [readsOnCommitTime] into
* the Tx object. However, that information is not available
* when we are running on a cluster per the ticket below.
* While we COULD tunnel this when running tasks on a
* Journal, we tend not to do that. Tasks tend to be
* submitted primarily for the clustered database
* deployment.
*
* @see http://trac.blazegraph.com/ticket/266
* (refactor native long tx id to thin object)
*
* @see <a
* href="http://trac.blazegraph.com/ticket/546"
* > Add cache for access to historical index views on the
* Journal by name and commitTime. </a>
*/
new Tx(transactionManager, resourceManager, timestamp,
timestamp/* readsOnCommitTime */);
}
}
tx = transactionManager.getTx(timestamp);
if (tx == null) {
throw new IllegalStateException("Unknown tx: "+timestamp);
}
tx.lock.lock();
try {
if (!tx.isActive()) {
throw new IllegalStateException("Not active: " + tx);
}
} finally {
tx.lock.unlock();
}
taskCounters = this.concurrencyManager.countersTX;
} else if (TimestampUtility.isReadOnly(timestamp)) {
/*
* A lightweight historical read.
*/
tx = null;
taskCounters = this.concurrencyManager.countersHR;
} else {
/*
* Unisolated operation.
*/
tx = null;
taskCounters = this.concurrencyManager.countersUN;
}
}
/**
* The timestamp specified to the ctor. This effects which index checkpoints
* are available to the task and whether the index(s) are read-only or
* mutable.
*/
public long getTimestamp() {
return timestamp;
}
/**
* Returns a copy of the array of resources declared to the constructor.
*/
@Override
public String[] getResource() {
// clone so that people can not mess with the resource names.
return resource.clone();
}
/**
* Return the only declared resource.
*
* @return The declared resource.
*
* @exception IllegalStateException
* if more than one resource was declared.
*/
@Override
public String getOnlyResource() {
if (resource.length > 1)
throw new IllegalStateException("More than one resource was declared");
return resource[0];
}
/**
* Return <code>true</code> iff the task declared this as a resource.
*
* @param theRequestedResource
* The name of a resource.
*
* @return <code>true</code> iff <i>name</i> is a declared resource.
*
* @throws IllegalArgumentException
* if <i>name</i> is <code>null</code>.
*/
public boolean isResource(final String theRequestedResource) {
if (theRequestedResource == null)
throw new IllegalArgumentException();
for (String theDeclaredResource : resource) {
if (theDeclaredResource.equals(theRequestedResource)) {
/*
* Exact match. This resource was declared.
*/
return true;
}
/**
* Look for a prefix that spans one or more resources.
*
* Note: Supporting this requires us to support efficient scans of
* the indices in Name2Addr since getIndex(name) will fail if the
* Name2Addr entry has not been buffered within the [n2a] cache.
*
* @see <a href="http://trac.blazegraph.com/ticket/753" > HA
* doLocalAbort() should interrupt NSS requests and
* AbstractTasks </a>
* @see <a href="http://trac.blazegraph.com/ticket/566" > Concurrent
* unisolated operations against multiple KBs </a>
*/
if (theRequestedResource.startsWith(theDeclaredResource)) {
// Possible prefix match.
if (theRequestedResource.charAt(theDeclaredResource.length()) == '.') {
/*
* Prefix match.
*
* E.g., name:="kb.spo.osp" and the task declared the
* resource "kb". In this case, "kb" is a PREFIX of the
* declared resource and the next character is the separator
* character for the resource names (this last point is
* important to avoid unintended contention between
* namespaces such as "kb" and "kb1").
*/
return true;
}
}
}
return false;
}
/**
* Asserts that the <i>resource</i> is one of the resource(s) declared to
* the constructor. This is used to prevent tasks from accessing resources
* that they did not declare (and on which they may not hold a lock).
*
* @param resource
* A resource name.
*
* @return The resource name.
*
* @exception IllegalStateException
* if the <i>resource</i> was not declared to the
* constructor.
*/
protected String assertResource(final String resource) {
if (isResource(resource))
return resource;
throw new IllegalStateException("Not declared: task=" + getTaskName()
+ ", resource=" + resource + " is not in "
+ Arrays.toString(this.resource));
}
/**
* Assert that the task is {@link ITx#UNISOLATED}.
*
* @throws UnsupportedOperationException
* unless the task is {@link ITx#UNISOLATED}
*/
protected void assertUnisolated() {
if (timestamp == ITx.UNISOLATED) {
return;
}
throw new UnsupportedOperationException("Task is not unisolated");
}
/**
* Assert that the task is still running ({@link #aborted} is
* <code>false</code>).
*
* @throws RuntimeException
* wrapping an {@link InterruptedException} if the task has been
* interrupted.
*/
protected void assertRunning() {
if(aborted) {
/*
* The task has been interrupted by the write service which also
* sets the [aborted] flag since the interrupt status may have been
* cleared by looking at it.
*/
throw new RuntimeException(new InterruptedException());
}
}
/**
* Returns Task{taskName,timestamp,elapsed,resource[]}
*/
@Override
public String toString() {
return "Task{" + getTaskName() + ",timestamp="
+ TimestampUtility.toString(timestamp)+",resource="
+ Arrays.toString(resource) + "}";
}
/**
* Returns the name of the class by default.
*/
protected String getTaskName() {
return getClass().getName();
}
/**
* Implement the task behavior here.
* <p>
* Note: Long-running implementations MUST periodically test
* {@link Thread#interrupted()} and MUST throw an exception, such as
* {@link InterruptedException}, if they are interrupted. This behavior
* allows tasks to be canceled in a timely manner.
* <p>
* If you ignore or fail to test {@link Thread#interrupted()} then your task
* CAN NOT be aborted. If it is {@link Future#cancel(boolean)} with
* <code>false</code> then the task will run to completion even though it
* has been cancelled (but the {@link Future} will appear to have been
* cancelled).
* <p>
* If you simply <code>return</code> rather than throwing an exception
* then the {@link WriteExecutorService} will assume that your task
* completed and your (partial) results will be made restart-safe at the
* next commit!
*
* @return The object that will be returned by {@link #call()} iff the
* operation succeeds.
*
* @throws Exception
* The exception that will be thrown by {@link #call()} iff the
* operation fails.
*
* @exception InterruptedException
* This exception SHOULD be thrown if
* {@link Thread#interrupted()} becomes true during
* execution.
*/
abstract protected T doTask() throws Exception;
/**
* Adds the following fields to the {@link MDC} logging context:
* <dl>
* <dt>taskname</dt>
* <dd>The name of the task as reported by {@link #getTaskName()}.</dd>
* <dt>timestamp</dt>
* <dd>The {@link #timestamp} specified to the ctor.</dd>
* <dt>resources</dt>
* <dd>The named resource(s) specified to the ctor IFF logging @ INFO or
* above.</dd>
* </dl>
*/
protected void setupLoggingContext() {
// Add to the logging context for the current thread.
MDC.put("taskname", getTaskName());
MDC.put("timestamp", Long.valueOf(timestamp));
if(log.isInfoEnabled())
MDC.put("resources", Arrays.toString(resource));
}
/**
* Clear fields set by {@link #setupLoggingContext()} from the {@link MDC}
* logging context.
*/
protected void clearLoggingContext() {
MDC.remove("taskname");
MDC.remove("timestamp");
if(log.isInfoEnabled())
MDC.remove("resources");
}
/**
* Delegates the task behavior to {@link #doTask()}.
* <p>
* For an unisolated operation, this method provides safe commit iff the
* task succeeds and otherwise invokes abort() so that partial task
* executions are properly discarded. When possible, the original exception
* is re-thrown so that we do not encapsulate the cause unless it would
* violate our throws clause.
* <p>
* Commit and abort are NOT invoked for an isolated operation regardless of
* whether the operation succeeds or fails. It is the responsibility of the
* "client" to commit or abort a transaction as it sees fit.
* <p>
* Note: Exceptions that are thrown from here will be wrapped as
* {@link ExecutionException}s by the {@link ExecutorService}. Use
* {@link InnerCause} to test for these exceptions.
*
* @throws StaleLocatorException
* if the task requests an index partition which has been split,
* joined, or moved to another data service.
* @throws NoSuchIndexException
* if the task requests an index that is not registered on the
* data service.
* @throws InterruptedException
* can be thrown if the task is interrupted, for example while
* awaiting a lock, if the commit group is being discarded, or
* if the journal is being shutdown (which will cause the
* executor service running the task to be shutdown and thereby
* interrupt all running tasks).
*/
@Override
final public T call() throws Exception {
try {
/*
* Increment by the amount of time that the task waited on the queue
* before it began to execute.
*/
final long waitingTime = (System.nanoTime() - nanoTime_submitTask);
taskCounters.queueWaitingNanoTime.addAndGet(waitingTime);
setupLoggingContext();
final T ret = call2();
clearLoggingContext();
taskCounters.taskSuccessCount.incrementAndGet();
return ret;
} catch(Exception e) {
/*
* Note: covers RuntimeException, just not Throwable and the Error
* hierarchy (which you are not supposed to catch).
*/
taskCounters.taskFailCount.incrementAndGet();
throw e;
} finally {
taskCounters.taskCompleteCount.incrementAndGet();
// increment by the amount of time that the task was executing.
taskCounters.serviceNanoTime.addAndGet(nanoTime_finishedWork
- nanoTime_beginWork);
if (checkpointNanoTime != 0L) {
/*
* Increment by the time required to checkpoint the indices
* written on by the task (IFF a write task and the task
* completes normally).
*/
taskCounters.checkpointNanoTime.addAndGet(checkpointNanoTime);
}
/*
* Increment by the total time from submit to completion.
*
* Note: This measure would not report the commit waiting time or
* the commit service time. Therefore the [queuingNanoTime] is
* special cased for the WriteExecutorService - see afterTask() on
* that class.
*/
if (timestamp != ITx.UNISOLATED) {
taskCounters.queuingNanoTime.addAndGet(nanoTime_finishedWork
- nanoTime_submitTask);
}
}
}
final private T call2() throws Exception {
nanoTime_assignedWorker = System.nanoTime();
try {
if (!submitted.compareAndSet(false, true)) {
throw new ResubmitException(toString());
}
if (isReadWriteTx) {
if (log.isInfoEnabled())
log.info("Running read-write tx: timestamp=" + timestamp);
// if(tx.isReadOnly()) {
//
// try {
//
// nanoTime_beginWork = System.nanoTime();
//
// return doTask();
//
// } finally {
//
// nanoTime_finishedWork = System.nanoTime();
//
// // release hard references to named read-only indices.
//
// clearIndexCache();
//
// }
//
// }
/*
* Delegate handles handshaking for writable transactions.
*/
final Callable<T> delegate = new InnerReadWriteTxServiceCallable<T>(
this, tx);
return delegate.call();
}
if (readOnly) {
try {
nanoTime_beginWork = System.nanoTime();
return doTask();
} finally {
nanoTime_finishedWork = System.nanoTime();
// release hard references to the named read-only indices.
clearIndexCache();
if(log.isInfoEnabled()) log.info("Reader is done: "+this);
}
} else {
/*
* Handle unisolated write tasks, which need to coordinate with
* the lock manager for the unisolated indices.
*/
assert timestamp == ITx.UNISOLATED : "timestamp="+timestamp;
return doUnisolatedReadWriteTask();
}
} finally {
if(log.isInfoEnabled()) log.info("done: "+this);
}
}
/**
* Call {@link #doTask()} for an unisolated write task.
*
* @throws Exception
*/
private T doUnisolatedReadWriteTask() throws Exception {
// // lock manager.
// final LockManager<String> lockManager = concurrencyManager.getWriteService().getLockManager();
final Thread t = Thread.currentThread();
if(log.isInfoEnabled())
log.info("Unisolated write task: " + this + ", thread=" + t);
// // declare resource(s) to lock (exclusive locks are used).
// lockManager.addResource(resource);
//
// // delegate will handle lock acquisition and invoke doTask().
// final LockManagerTask<String,T> delegate = new LockManagerTask<String,T>(lockManager,
// resource, new InnerWriteServiceCallable(this));
final Callable<T> delegate = new InnerWriteServiceCallable<T>(this);
final WriteExecutorService writeService = concurrencyManager
.getWriteService();
if (verifyLocks && resource.length > 0
&& writeService.getLockManager().getTaskWithLocks(resource) == null) {
/*
* Note: The most common reason for this exception is that you did
* not submit the task to the concurrency manager and hence it was
* not processed by the lock manager. E.g., you invoked call()
* directly on some subclass of AbstractTask. That is a no-no.
*/
throw new AssertionError("Task does not hold its locks: " + this);
}
writeService.beforeTask(t, this);
boolean ran = false;
try {
final T ret;
/*
* By the time the call returns any lock(s) have been released.
*
* Note: Locks MUST be released as soon as the task is done writing
* so that it does NOT hold locks while it is awaiting commit. This
* make it possible for other operations to write on the same index
* in the same commit group.
*/
// try {
ret = delegate.call();
// } finally {
//
// /*
// * Increment by the amount of time that the task was waiting to
// * acquire its lock(s).
// */
//
// taskCounters.lockWaitingTime.addAndGet( delegate.getLockLatency() );
//
// }
if (Thread.interrupted()) {
/*
* @todo why test after return? if the task ran, it ran, right?
*/
throw new InterruptedException();
}
// set flag.
ran = true;
if (log.isInfoEnabled())
log.info("Task Ok: class=" + this);
/*
* Note: The WriteServiceExecutor will await a commit signal before
* the thread is allowed to complete. This ensures that the caller
* waits until a commit (or an abort).
*
* Note: Waiting here does NOT prevent other tasks from gaining
* access to the same resources since the locks were released above.
*/
writeService.afterTask(this/* task */, null/* cause */);
return ret;
} catch (Throwable t2) {
if (!ran) {
// Note: Do not re-invoke afterTask() if it failed above.
if (log.isInfoEnabled())
log.info("Task failed: class=" + this + " : " + t2);
writeService.afterTask(this/* task */, t2/* cause */);
}
/*
* Throw whatever exception was thrown by the task (or by afterTask
* if it craps out).
*/
if (t2 instanceof Exception)
throw (Exception) t2;
throw new RuntimeException(t2);
} finally {
// discard hard references to accessed indices.
clearIndexCache();
}
}
/**
* Delegates various behaviors visible to the application code using the
* {@link ITask} interface to the {@link AbstractTask} object.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
*/
static abstract protected class DelegateTask<T> implements ITask<T> {
final protected AbstractTask<T> delegate;
protected DelegateTask(final AbstractTask<T> delegate) {
if (delegate == null)
throw new IllegalArgumentException();
this.delegate = delegate;
}
@Override
public IResourceManager getResourceManager() {
return delegate.getResourceManager();
}
@Override
public IJournal getJournal() {
return delegate.getJournal();
}
@Override
public String[] getResource() {
return delegate.getResource();
}
@Override
public String getOnlyResource() {
return delegate.getOnlyResource();
}
@Override
public IIndex getIndex(String name) {
return delegate.getIndex(name);
}
@Override
public TaskCounters getTaskCounters() {
return delegate.getTaskCounters();
}
@Override
public String toString() {
return getClass().getName() + "(" + delegate.toString() + ")";
}
}
/**
* Inner class used to wrap up the call to {@link AbstractTask#doTask()} for
* read-write transactions.
*/
static protected class InnerReadWriteTxServiceCallable<T> extends DelegateTask<T> {
private final Tx tx;
InnerReadWriteTxServiceCallable(final AbstractTask<T> delegate, final Tx tx) {
super( delegate );
if (tx == null)
throw new IllegalArgumentException();
this.tx = tx;
}
/**
* Wraps up the execution of {@link AbstractTask#doTask()}.
*/
@Override
public T call() throws Exception {
// invoke on the outer class.
try {
delegate.nanoTime_beginWork = System.nanoTime();
tx.lock.lock();
try {
return delegate.doTask();
} finally {
tx.lock.unlock();
}
} finally {
delegate.nanoTime_finishedWork = System.nanoTime();
/*
* Release hard references to the named indices. The backing
* indices are reading from the ground state identified by the
* start time of the ReadWrite transaction.
*/
delegate.clearIndexCache();
}
}
}
/**
* An instance of this class is used as the delegate to coordinate the acquisition of locks
* with the {@link NonBlockingLockManager} before the task can execute and to release
* locks after the task has completed (whether it succeeds or fails).
* <p>
* Note: This inner class delegates the execution of the task to
* {@link AbstractTask#doTask()} on the outer class.
* <p>
* Note: If there is only a single writer thread then the lock system
* essentially does nothing. When there are multiple writer threads the lock
* system imposes a partial ordering on the writers that ensures that writes
* on a given named index are single-threaded and that deadlocks do not
* prevent tasks from progressing. If there is strong lock contention then
* writers will be more or less serialized.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
*/
static protected class InnerWriteServiceCallable<T> extends DelegateTask<T> {
InnerWriteServiceCallable(final AbstractTask<T> delegate) {
super(delegate);
}
/**
* Note: Locks on the named indices are ONLY held during this call.
*/
@Override
public T call() throws Exception {
// The write service on which this task is running.
final WriteExecutorService writeService = delegate.concurrencyManager
.getWriteService();
// setup view of the declared resources.
delegate.setupIndices();
delegate.nanoTime_beginWork = System.nanoTime();
writeService.activeTaskCountWithLocksHeld.incrementAndGet();
try {
// invoke doTask() on AbstractTask with locks.
final T ret = delegate.doTask();
// checkpoint while holding locks.
delegate.checkpointNanoTime = delegate.checkpointTask();
return ret;
} catch(Throwable t) {
/*
* RWStore: If there is an error in the task execution, then for
* RWStore we need to explicitly undo the allocations for the
* B+Tree(s) on which this task wrote. If we do not take this
* step, then the records already written onto the store up to
* that point will never be released if the groupCommit
* succeeds. This is essentially a persistent memory leak on the
* store.
*/
delegate.abortTask();
// rethrow the exception.
throw new RuntimeException(t);
} finally {
/*
* @todo This is the ONLY place where it would be safe to handle
* after actions while holding the lock. As soon as we leave
* this method we have lost the exclusive lock on the index and
* another task could be running on it before we can do anything
* else. However, note that an after action typically requires
* that the task has already been committed or aborted. In order
* to support such after actions we would have to wait until the
* abort or commit before releasing the lock.
*/
delegate.nanoTime_finishedWork = System.nanoTime();
writeService.activeTaskCountWithLocksHeld.decrementAndGet();
try {
/*
* Release the locks held by the task.
*/
writeService.getLockManager().releaseLocksForTask(
delegate.resource);
} catch (Throwable t) {
// log an error but do not abort the task.
if (verifyLocks)
log.error(delegate, t); // log as an error.
// fall through
}
}
}
}
/**
* This is thrown if you attempt to reuse (re-submit) the same
* {@link AbstractTask} instance.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
*/
public static class ResubmitException extends RejectedExecutionException {
/**
*
*/
private static final long serialVersionUID = -8661545948587322943L;
public ResubmitException() {
super();
}
public ResubmitException(final String msg) {
super(msg);
}
}
/**
* A read-write view of an {@link IJournal} that is used to impose isolated
* and atomic changes for {@link ITx#UNISOLATED} tasks that register or drop
* indices. The intentions of the task are buffered locally (by this class)
* for that task such that the index appears to have been registered or
* dropped immediately. Those intentions are propagated to {@link Name2Addr}
* on a task-by-task basis only when (a) this task is part of the current
* commit group; and (b) the {@link WriteExecutorService} performs a group
* commit. If there is a conflict between the state of {@link Name2Addr} at
* the time of the commit and the intentions of the tasks in the commit
* group (for example, if two tasks try to drop the same index), then the
* 2nd task will be interrupted. The enumeration of the intentions of tasks
* always begins with the task executed by the thread performing the commit
* so that no other task's intention could cause the commit itself to be
* interrupted.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
*/
class IsolatedActionJournal implements IJournal {
private final AbstractJournal delegate;
private final IAllocationContext context;
@SuppressWarnings("rawtypes")
private final IResourceLocator resourceLocator;
private final GlobalRowStoreHelper globalRowStoreHelper;
@Override
public String toString() {
return getClass().getName() + "{task=" + AbstractTask.this + "}";
}
public void prepareCommit() {
// final IBufferStrategy bufferStrategy = delegate.getBufferStrategy();
// if (bufferStrategy instanceof RWStrategy) {
// ((RWStrategy) bufferStrategy).getRWStore().activateTx();
// }
// now detach!
detachContext();
}
// RawTx encapsulates the transaction protocol to prevent multiple calls to close()
private final IRawTx m_rawTx;
public void completeTask() {
if (m_rawTx != null) {
m_rawTx.close();
}
}
/**
* This class prevents {@link ITx#UNISOLATED} tasks from having direct
* access to the {@link AbstractJournal} using
* {@link AbstractTask#getJournal()}.
*
* @param source
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public IsolatedActionJournal(final AbstractJournal source) {
if (source == null)
throw new IllegalArgumentException();
// Note: The raw journal.
this.delegate = source;
// Note: isolated by this class.
this.globalRowStoreHelper = new GlobalRowStoreHelper(this);
/*
* Setup a locator for resources. Resources that correspond to indices
* declared by the task are accessible via the task itself.
*
* Note: This SHOULD NOT expose the underlying Journal's resource
* locator since that can allow a class to register an index without
* going through this IsolatedActionJournal. In particular, the
* GlobalRowStoreHelper.getGlobalRowStore() can cause the GRS to be
* registered on the underlying index. This was breaking HA starts when
* I first converted the code to support group commit at the NSS layer.
*
* TODO I have left in support for the case where the underlying
* Journal the ManagedJournal associated with a DataService in a
* federation. While I have not verified it, the following comment
* clearly contemplates providing access to non-remote resources in
* this manner. "Other resources are assessible via the locator on the
* underlying journal. When the journal is part of a federation, that
* locator will be the federation's locator." I am not sure if this
* comment reflects a sensible design decision or a Bad Idea, in which
* case the delegate locator should be [null] for the federation as
* well.
*/
resourceLocator = new DefaultResourceLocator(//
this,// IndexManager (IsolatedActionJournal)
((source instanceof ManagedJournal) ? source
.getResourceLocator() : null/* DO-NOT-DELEGATE */)//
);
final IBufferStrategy bufferStrategy = source.getBufferStrategy();
if (bufferStrategy instanceof IRWStrategy) {
// must grab the tx BEFORE registering the context to correctly
// bracket, since the tx count is decremented AFTER the
// context is released
m_rawTx = ((IRWStrategy) bufferStrategy).newTx();
context = ((IRWStrategy) bufferStrategy).newAllocationContext(true /*isolated*/);
if (context == null) {
throw new AssertionError("Null context returned by " + bufferStrategy.getClass().getName());
}
} else {
m_rawTx = null;
context = null;
}
}
/*
* Overridden methods for registering or dropping indices.
*/
/**
* Delegates to the {@link AbstractTask}.
*/
@Override
public void dropIndex(final String name) {
AbstractTask.this.dropIndex(name);
}
/**
* Note: This is the core implementation for registering an index - it
* delegates to the {@link AbstractTask}.
*/
@Override
public IIndex registerIndex(final String name, final BTree btree) {
return AbstractTask.this.registerIndex(name, btree);
}
@Override
public ICheckpointProtocol register(final String name, final IndexMetadata metadata) {
/*
* FIXME GIST : Support registration of index types other than BTree
* (HTree, Stream, etc).
*
* @see http://trac.blazegraph.com/ticket/585 (GIST)
*/
throw new UnsupportedOperationException();
}
@Override
public void registerIndex(final IndexMetadata indexMetadata) {
// delegate to core impl.
registerIndex(indexMetadata.getName(), indexMetadata);
}
/**
* FIXME GIST This needs to use logic to handle the different types of
* index objects that we can create. That logic is currently centralized
* in {@link AbstractJournal#register(String, IndexMetadata)}. The same
* pattern needs to be put to use here.
*/
@Override
public IIndex registerIndex(final String name,
final IndexMetadata indexMetadata) {
// Note: handles constraints and defaults for index partitions.
delegate.validateIndexMetadata(name, indexMetadata);
// Note: create on the _delegate_.
final BTree btree = BTree.create(delegate, indexMetadata);
// delegate to core impl.
return registerIndex(name, btree);
}
/**
* Note: access to an unisolated index is governed by the AbstractTask.
*/
@Override
public ICheckpointProtocol getUnisolatedIndex(final String name) {
try {
/*
* FIXME GIST. This will throw a ClassCastException if the
* returned index is an ILocalBTreeView.
*
* @see http://trac.blazegraph.com/ticket/585 (GIST)
*/
return (ICheckpointProtocol) AbstractTask.this.getIndex(name);
} catch(NoSuchIndexException ex) {
// api conformance.
return null;
}
}
/**
* Note: access to an unisolated index is governed by the AbstractTask.
*/
@Override
public IIndex getIndex(final String name) {
try {
return AbstractTask.this.getIndex(name);
} catch(NoSuchIndexException ex) {
// api conformance.
return null;
}
}
/**
* Note: you are allowed access to historical indices without having to
* declare a lock - such views will always be read-only and support
* concurrent readers.
*/
@Override
public ICheckpointProtocol getIndexLocal(final String name,
final long commitTime) {
if (commitTime == ITx.UNISOLATED) {
return getUnisolatedIndex(name);
}
/*
* The index view is obtained from the resource manager.
*/
if (resourceManager instanceof IJournal) {
/*
* This code path supports any type of index (BTree, HTree, etc).
*
* Note: It does NOT support ILocalBTreeView (e.g., FusedView,
* IsolatedFusedView).
*/
return ((IJournal) resourceManager).getIndexLocal(name,
commitTime);
}
/**
* FIXME GIST : This code path only supports BTree
* (ILocalBTreeView). An attempt to resolve an HTree or other
* non-BTree based named index data structure will probably result
* in a ClassCastException.
*
* @see <a
* href="http://trac.blazegraph.com/ticket/585"
* > GIST </a>
*/
return (ICheckpointProtocol) resourceManager.getIndex(name, commitTime);
}
@Override
public IIndex getIndex(final String name, final long timestamp) {
if(TimestampUtility.isReadWriteTx(timestamp)) {
/*
* This code path supports read/write transactions. The interface
* returned for a read/write transaction is an ILocalBTreeView and
* will be an IsolatableFusedView (extends FusedView). These classes
* do not implement ICheckpointProtocol. Therefore we can not
* delegate this method to getIndexLocal() which returns an
* ICheckpointProtocol instance.
*
* See #1156 (Read/write tx support in REST API).
*/
return resourceManager.getIndex(name, timestamp);
}
return (IIndex) getIndexLocal(name, timestamp);
}
/**
* Returns an {@link ITx#READ_COMMITTED} view if the index exists -or-
* an {@link ITx#UNISOLATED} view IFF the {@link AbstractTask} declared
* the name of the backing index as one of the resources for which it
* acquired a lock.
*/
@Override
public SparseRowStore getGlobalRowStore() {
// did the task declare the resource name?
if(isResource(GlobalRowStoreHelper.GLOBAL_ROW_STORE_INDEX)) {
/*
* Return an unisolated view of the index, but the view is still
* scoped by the task. The index will be created if it does not
* exist.
*/
return globalRowStoreHelper.getGlobalRowStore();
}
// read committed view IFF it exists otherwise [null]
// TODO Review. Make sure we have tx protection to avoid recycling of the view.
final long lastCommitTime = getLastCommitTime();
return globalRowStoreHelper.get(lastCommitTime);
//return new GlobalRowStoreHelper(this).get(ITx.READ_COMMITTED);
}
@Override
public SparseRowStore getGlobalRowStore(final long timestamp) {
if (!TimestampUtility.isReadOnly(timestamp)) {
// A request for a mutable view.
return getGlobalRowStore();
}
/*
* Note: This goes around getIndex(name,timestamp) on this method
* and uses that method on the delegate. This is because of the
* restriction on access to declared indices. It's Ok to go around
* like this since you do not need a lock for a read-only view.
*/
// return new GlobalRowStoreHelper(this).getReadCommitted();
final IIndex ndx = delegate.getIndex(
GlobalRowStoreHelper.GLOBAL_ROW_STORE_INDEX, timestamp);
if (ndx != null) {
return new SparseRowStore(ndx);
}
return null;
}
/**
* Returns an {@link ITx#READ_COMMITTED} view if the file system exists
* -or- an {@link ITx#UNISOLATED} view IFF the {@link AbstractTask}
* declared the names of the backing indices as resources for which it
* acquired a lock.
*/
@Override
public BigdataFileSystem getGlobalFileSystem() {
// did the task declare the resource name?
final String namespace = GlobalFileSystemHelper.GLOBAL_FILE_SYSTEM_NAMESPACE;
if (isResource(namespace + "."+BigdataFileSystem.FILE_METADATA_INDEX_BASENAME)
&& isResource(namespace + "."+BigdataFileSystem.FILE_DATA_INDEX_BASENAME)) {
// unisolated view - will create if it does not exist.
return new GlobalFileSystemHelper(this).getGlobalFileSystem();
}
// read committed view IFF it exists otherwise [null]
return new GlobalFileSystemHelper(this).getReadCommitted();
}
/**
* Returns an {@link TemporaryStore} local to a specific
* {@link AbstractTask}.
* <p>
* Note: While data can not be shared across {@link AbstractTask}s using
* the returned {@link TemporaryStore}, you can create an
* {@link ILocatableResource} on a {@link TemporaryStore} and then
* locate it from within the {@link AbstractTask}. This has the
* advantage that the isolation and read/write constraints of the
* {@link AbstractTask} will be imposed on access to the
* {@link ILocatableResource}s.
*
* FIXME GIST Reconsider the inner journal classes on AbstractTask. This
* is a heavy weight mechanism for enforcing isolation for temporary
* stores. It would be better to have isolation in the locator mechanism
* itself. This will especially effect scale-out query using temporary
* stores and will break semantics when the task is isolated by a
* transaction rather than unisolated.
*/
@Override
public TemporaryStore getTempStore() {
return tempStoreFactory.getTempStore();
}
final private TemporaryStoreFactory tempStoreFactory = new TemporaryStoreFactory();
@Override
public IResourceLocator<?> getResourceLocator() {
return resourceLocator;
}
@Override
public ILocalTransactionManager getLocalTransactionManager() {
return delegate.getLocalTransactionManager();
}
@Override
public IResourceLockService getResourceLockService() {
return delegate.getResourceLockService();
}
@Override
public ExecutorService getExecutorService() {
return delegate.getExecutorService();
}
@Override
public Quorum<HAGlue,QuorumService<HAGlue>> getQuorum() {
return delegate.getQuorum();
}
@Override
final public long awaitHAReady(final long timeout, final TimeUnit units)
throws InterruptedException, TimeoutException,
AsynchronousQuorumCloseException {
return delegate.awaitHAReady(timeout, units);
}
/*
* Disallowed methods (commit protocol and shutdown protocol).
*/
/**
* {@inheritDoc}
* <p>
* Marks the task as aborted. The task will not commit. However, the
* task will continue to execute until control returns from its
* {@link AbstractTask#doTask()} method.
*/
@Override
public void abort() {
aborted = true;
}
/**
* {@inheritDoc}
* <p>
* Overridden as NOP. Tasks do not directly invoke commit() on the
* Journal.
*/
@Override
public long commit() {
if (aborted)
throw new IllegalStateException("aborted");
return 0;
}
@Override
public void close() {
throw new UnsupportedOperationException();
}
@Override
public void destroy() {
throw new UnsupportedOperationException();
}
@Override
public void deleteResources() {
throw new UnsupportedOperationException();
}
@Override
public void setCommitter(int index, ICommitter committer) {
throw new UnsupportedOperationException();
}
@Override
public void shutdown() {
throw new UnsupportedOperationException();
}
@Override
public void shutdownNow() {
throw new UnsupportedOperationException();
}
/*
* Methods which delegate directly to the live journal.
*/
// public IKeyBuilder getKeyBuilder() {
// return delegate.getKeyBuilder();
// }
@Override
public void force(final boolean metadata) {
delegate.force(metadata);
}
@Override
public int getByteCount(final long addr) {
return delegate.getByteCount(addr);
}
@Override
public ICommitRecord getCommitRecord(final long timestamp) {
return delegate.getCommitRecord(timestamp);
}
@Override
public CounterSet getCounters() {
return delegate.getCounters();
}
@Override
public File getFile() {
return delegate.getFile();
}
@Override
public long getOffset(final long addr) {
return delegate.getOffset(addr);
}
@Override
public long getPhysicalAddress(final long addr) {
return delegate.getPhysicalAddress(addr);
}
@Override
public Properties getProperties() {
return delegate.getProperties();
}
@Override
public UUID getUUID() {
return delegate.getUUID();
}
@Override
public IResourceMetadata getResourceMetadata() {
return delegate.getResourceMetadata();
}
@Override
public long getRootAddr(final int index) {
return delegate.getRootAddr(index);
}
@Override
public long getLastCommitTime() {
return delegate.getLastCommitTime();
}
@Override
public IRootBlockView getRootBlockView() {
return delegate.getRootBlockView();
}
@Override
public boolean isFullyBuffered() {
return delegate.isFullyBuffered();
}
@Override
public boolean isOpen() {
return delegate.isOpen();
}
@Override
public boolean isReadOnly() {
return delegate.isReadOnly();
}
@Override
public boolean isStable() {
return delegate.isStable();
}
// public void packAddr(DataOutput out, long addr) throws IOException {
// delegate.packAddr(out, addr);
// }
@Override
public ByteBuffer read(final long addr) {
return delegate.read(addr);
}
@Override
public long size() {
return delegate.size();
}
@Override
public long toAddr(final int nbytes, final long offset) {
return delegate.toAddr(nbytes, offset);
}
@Override
public String toString(final long addr) {
return delegate.toString(addr);
}
// @Override
// public IRootBlockView getRootBlock(final long commitTime) {
// return delegate.getRootBlock(commitTime);
// }
//
// public Iterator<IRootBlockView> getRootBlocks(final long startTime) {
// return delegate.getRootBlocks(startTime);
// }
/*
* IAllocationContext
*
* The journal write() and delete() methods are overridden here to use
* the IsolatedActionJournal as the IAllocationContext. This causes the
* allocations to be scoped to the AbstractTask.
*/
@Override
public long write(final ByteBuffer data) {
return delegate.write(data, context);
}
// @Override
// public long write(final ByteBuffer data, final long oldAddr) {
// return delegate.write(data, oldAddr, this);
// }
@Override
public IPSOutputStream getOutputStream() {
return delegate.getOutputStream(context);
}
@Override
public InputStream getInputStream(long addr) {
return delegate.getInputStream(addr);
}
@Override
public void delete(final long addr) {
delegate.delete(addr, context);
}
// public void delete(long addr, IAllocationContext context) {
// delegate.delete(addr, context);
// }
//
// public long write(ByteBuffer data, IAllocationContext context) {
// return delegate.write(data, context);
// }
//
// public long write(ByteBuffer data, long oldAddr, IAllocationContext context) {
// return delegate.write(data, oldAddr, context);
// }
public void detachContext() {
delegate.detachContext(context);
}
public void abortContext() {
delegate.abortContext(context);
completeTask();
}
@Override
public ScheduledFuture<?> addScheduledTask(final Runnable task,
final long initialDelay, final long delay, final TimeUnit unit) {
return delegate.addScheduledTask(task, initialDelay, delay, unit);
}
@Override
public boolean getCollectPlatformStatistics() {
return delegate.getCollectPlatformStatistics();
}
@Override
public boolean getCollectQueueStatistics() {
return delegate.getCollectQueueStatistics();
}
@Override
public int getHttpdPort() {
return delegate.getHttpdPort();
}
/**
* {@inheritDoc}
* <p>
* Overridden to visit the name of all indices that were isolated and to
* ignore the timestamp.
*/
@SuppressWarnings("unchecked")
@Override
public Iterator<String> indexNameScan(final String prefix,
final long timestampIsIgnored) {
return new Striterator(n2a.values().iterator())
.addFilter(new Filter(){
private static final long serialVersionUID = 1L;
/*
* Impose prefix restriction on the Name2Addr scan.
*
* TODO This forces us to scan all indices that were isolated
* by the task. When using hierarchical locking that is
* typically on the order of ~10 indices. If also using
* durable named solution sets, then this could be quite a bit
* more and it might be worthwhile to make [n2a] on
* AbstractTask a TreeMap (using the same comparator
* semantics) such that the prefix scan could be turned into a
* range restricted scan.
*/
@Override
public boolean isValid(Object obj) {
return ((Entry)obj).name.startsWith(prefix);
}}).addFilter(new Resolver() {
private static final long serialVersionUID = 1L;
/*
* Resolve Entry to the name of the index.
*/
@Override
protected Object resolve(final Object obj) {
return ((Entry)obj).name;
}
});
}
@Override
public boolean isDirty() {
return delegate.isDirty();
}
@Override
public boolean isGroupCommit() {
return delegate.isGroupCommit();
}
@Override
public boolean isHAJournal() {
return false;
}
} // class IsolatatedActionJournal
/**
* A read-only view of an {@link IJournal} that is used to enforce read-only
* semantics on tasks using {@link AbstractTask#getJournal()} to access the
* backing store. Methods that write on the journal, that expose the
* unisolated indices, or which are part of the commit protocol will throw
* an {@link UnsupportedOperationException}.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
*/
private class ReadOnlyJournal implements IJournal {
private final IJournal delegate;
@SuppressWarnings("rawtypes")
private final DefaultResourceLocator resourceLocator;
@Override
public String toString() {
return getClass().getName() + "{task=" + AbstractTask.this + "}";
}
@Override
public boolean isHAJournal() {
return false;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public ReadOnlyJournal(final AbstractJournal source) {
if (source == null)
throw new IllegalArgumentException();
this.delegate = source;
/*
* Setup a locator for resources. Resources that correspond to
* indices declared by the task are accessible via the task itself.
* Other resources are accessible via the locator on the underlying
* journal. When the journal is part of a federation, that locator
* will be the federation's locator.
*/
resourceLocator = new DefaultResourceLocator(//
this, // IndexManager
source.getResourceLocator()// delegate locator
);
}
/*
* Index access methods (overridden or disallowed depending on what they
* do).
*/
@Override
public IIndex getIndex(final String name, final long timestamp) {
if (timestamp == ITx.UNISOLATED)
throw new UnsupportedOperationException();
if (timestamp == AbstractTask.this.timestamp) {
// to the AbstractTask
try {
return AbstractTask.this.getIndex(name);
} catch(NoSuchIndexException ex) {
// api conformance.
return null;
}
}
// to the backing journal.
return (IIndex) delegate.getIndexLocal(name, timestamp);
}
/**
* {@inheritDoc}
* <p>
* Note: Does not allow access to {@link ITx#UNISOLATED} indices.
*/
@Override
public ICheckpointProtocol getIndexLocal(final String name,
final long commitTime) {
if (timestamp == ITx.UNISOLATED)
throw new UnsupportedOperationException();
if(timestamp == AbstractTask.this.timestamp) {
// to the AbstractTask
try {
/*
* FIXME GIST : This will throw a ClassCastException if the
* index type is ReadCommittedIndex or FusedView.
*/
return (ICheckpointProtocol) AbstractTask.this
.getIndex(name);
} catch(NoSuchIndexException ex) {
// api conformance.
return null;
}
}
// to the backing journal.
return delegate.getIndexLocal(name, timestamp);
}
/**
* {@inheritDoc}
* <p>
* Note: Does not allow access to {@link ITx#UNISOLATED} indices.
*/
@Override
public Iterator<String> indexNameScan(final String prefix,
final long timestamp) {
if (timestamp == ITx.UNISOLATED)
throw new UnsupportedOperationException();
// to the backing journal.
return delegate.indexNameScan(prefix, timestamp);
}
/**
* Note: Not supported since this method returns the
* {@link ITx#UNISOLATED} index.
*/
@Override
public ICheckpointProtocol getUnisolatedIndex(String name) {
throw new UnsupportedOperationException();
}
/**
* Note: Not supported since this method returns the
* {@link ITx#UNISOLATED} index.
*/
@Override
public IIndex getIndex(String name) {
throw new UnsupportedOperationException();
}
@Override
public void dropIndex(String name) {
throw new UnsupportedOperationException();
}
@Override
public ICheckpointProtocol register(String name, IndexMetadata metadata) {
throw new UnsupportedOperationException();
}
@Override
public void registerIndex(IndexMetadata indexMetadata) {
throw new UnsupportedOperationException();
}
@Override
public IIndex registerIndex(String name, BTree btree) {
throw new UnsupportedOperationException();
}
@Override
public IIndex registerIndex(String name, IndexMetadata indexMetadata) {
throw new UnsupportedOperationException();
}
/**
* Returns an {@link ITx#READ_COMMITTED} view iff the index exists and
* <code>null</code> otherwise.
*/
@Override
public SparseRowStore getGlobalRowStore() {
/*
* Note: This goes around getIndex(name,timestamp) on this method
* and uses that method on the delegate. This is because of the
* restriction on access to declared indices. It's Ok to go around
* like this since you do not need a lock for a read-only view.
*/
// return new GlobalRowStoreHelper(this).getReadCommitted();
// last commit time.
final long lastCommitTime = delegate.getRootBlockView()
.getLastCommitTime();
final IIndex ndx = delegate.getIndex(
GlobalRowStoreHelper.GLOBAL_ROW_STORE_INDEX,
TimestampUtility.asHistoricalRead(lastCommitTime));
if (ndx != null) {
return new SparseRowStore(ndx);
}
return null;
}
@Override
public SparseRowStore getGlobalRowStore(final long timestamp) {
/*
* Note: This goes around getIndex(name,timestamp) on this method
* and uses that method on the delegate. This is because of the
* restriction on access to declared indices. It's Ok to go around
* like this since you do not need a lock for a read-only view.
*/
// return new GlobalRowStoreHelper(this).getReadCommitted();
if (!TimestampUtility.isReadOnly(timestamp)) {
throw new IllegalArgumentException(
"Only read-only views are supported: timestamp="
+ TimestampUtility.toString(timestamp));
}
final IIndex ndx = delegate.getIndex(
GlobalRowStoreHelper.GLOBAL_ROW_STORE_INDEX, timestamp);
if (ndx != null) {
return new SparseRowStore(ndx);
}
return null;
}
/**
* Returns an {@link ITx#READ_COMMITTED} view iff the file system exists
* and <code>null</code> otherwise.
*/
@Override
public BigdataFileSystem getGlobalFileSystem() {
/*
* Note: This goes around getIndex(name,timestamp) on this method
* and uses that method on the delegate. This is because of the
* restriction on access to declared indices. It's Ok to go around
* like this since you do not need a lock for a read-only view.
*/
// return new GlobalRowStoreHelper(this).getReadCommitted();
final IIndexManager tmp = new DelegateIndexManager(this) {
@Override
public IIndex getIndex(final String name, final long timestampIsIgnored) {
// last commit time.
final long commitTime = delegate.getRootBlockView()
.getLastCommitTime();
return delegate.getIndex(name, TimestampUtility
.asHistoricalRead(commitTime));
}
};
return new GlobalFileSystemHelper(tmp).getReadCommitted();
}
/**
* Returns an {@link TemporaryStore} local to a specific
* {@link AbstractTask}.
* <p>
* Note: While data can not be shared across {@link AbstractTask}s using
* the returned {@link TemporaryStore}, you can create an
* {@link ILocatableResource} on a {@link TemporaryStore} and then
* locate it from within the {@link AbstractTask}. This has the
* advantage that the isolation and read/write constraints of the
* {@link AbstractTask} will be imposed on access to the
* {@link ILocatableResource}s.
*
* FIXME Reconsider the inner journal classes on AbstractTask. This is a
* heavy weight mechanism for enforcing isolation for temporary stores.
* It would be better to have isolation in the locator mechanism itself.
* This will especially effect scale-out query using temporary stores
* and will break semantics when the task is isolated by a transaction
* rather than unisolated.
*/
@Override
public TemporaryStore getTempStore() {
return tempStoreFactory.getTempStore();
}
private TemporaryStoreFactory tempStoreFactory = new TemporaryStoreFactory();
@Override
public DefaultResourceLocator<?> getResourceLocator() {
return resourceLocator;
}
@Override
public ILocalTransactionManager getLocalTransactionManager() {
return delegate.getLocalTransactionManager();
}
@Override
public IResourceLockService getResourceLockService() {
return delegate.getResourceLockService();
}
@Override
public ExecutorService getExecutorService() {
return delegate.getExecutorService();
}
/*
* Disallowed methods (commit and shutdown protocols).
*/
@Override
public void abort() {
throw new UnsupportedOperationException();
}
@Override
public void close() {
throw new UnsupportedOperationException();
}
@Override
public void destroy() {
throw new UnsupportedOperationException();
}
@Override
public Quorum<HAGlue,QuorumService<HAGlue>> getQuorum() {
throw new UnsupportedOperationException();
}
@Override
final public long awaitHAReady(final long timeout, final TimeUnit units)
throws InterruptedException, TimeoutException,
AsynchronousQuorumCloseException {
return delegate.awaitHAReady(timeout, units);
}
@Override
public long commit() {
throw new UnsupportedOperationException();
}
@Override
public void deleteResources() {
throw new UnsupportedOperationException();
}
@Override
public void setCommitter(int index, ICommitter committer) {
throw new UnsupportedOperationException();
}
@Override
public void shutdown() {
throw new UnsupportedOperationException();
}
@Override
public void shutdownNow() {
throw new UnsupportedOperationException();
}
/*
* Disallowed methods (methods that write on the store).
*/
@Override
public void force(boolean metadata) {
throw new UnsupportedOperationException();
}
@Override
public long write(ByteBuffer data) {
throw new UnsupportedOperationException();
}
// @Override
// public long write(ByteBuffer data, long oldAddr) {
// throw new UnsupportedOperationException();
// }
@Override
public void delete(long addr) {
throw new UnsupportedOperationException();
}
/*
* Methods that delegate directly to the backing journal.
*/
@Override
public int getByteCount(long addr) {
return delegate.getByteCount(addr);
}
@Override
public ICommitRecord getCommitRecord(long timestamp) {
return delegate.getCommitRecord(timestamp);
}
@Override
public CounterSet getCounters() {
return delegate.getCounters();
}
@Override
public File getFile() {
return delegate.getFile();
}
@Override
public long getOffset(long addr) {
return delegate.getOffset(addr);
}
@Override
public long getPhysicalAddress(final long addr) {
return delegate.getPhysicalAddress(addr);
}
@Override
public Properties getProperties() {
return delegate.getProperties();
}
@Override
public UUID getUUID() {
return delegate.getUUID();
}
@Override
public IResourceMetadata getResourceMetadata() {
return delegate.getResourceMetadata();
}
@Override
public long getRootAddr(int index) {
return delegate.getRootAddr(index);
}
@Override
public long getLastCommitTime() {
return delegate.getLastCommitTime();
}
@Override
public IRootBlockView getRootBlockView() {
return delegate.getRootBlockView();
}
@Override
public boolean isFullyBuffered() {
return delegate.isFullyBuffered();
}
@Override
public boolean isOpen() {
return delegate.isOpen();
}
@Override
public boolean isReadOnly() {
return delegate.isReadOnly();
}
@Override
public boolean isStable() {
return delegate.isStable();
}
@Override
public ByteBuffer read(long addr) {
return delegate.read(addr);
}
@Override
public long size() {
return delegate.size();
}
@Override
public long toAddr(int nbytes, long offset) {
return delegate.toAddr(nbytes, offset);
}
@Override
public String toString(long addr) {
return delegate.toString(addr);
}
// @Override
// public IRootBlockView getRootBlock(long commitTime) {
// return delegate.getRootBlock(commitTime);
// }
//
// public Iterator<IRootBlockView> getRootBlocks(long startTime) {
// return delegate.getRootBlocks(startTime);
// }
@Override
public ScheduledFuture<?> addScheduledTask(Runnable task,
long initialDelay, long delay, TimeUnit unit) {
return delegate.addScheduledTask(task, initialDelay, delay, unit);
}
@Override
public boolean getCollectPlatformStatistics() {
return delegate.getCollectPlatformStatistics();
}
@Override
public boolean getCollectQueueStatistics() {
return delegate.getCollectQueueStatistics();
}
@Override
public int getHttpdPort() {
return delegate.getHttpdPort();
}
@Override
public IPSOutputStream getOutputStream() {
throw new UnsupportedOperationException();
}
@Override
public InputStream getInputStream(long addr) {
return delegate.getInputStream(addr);
}
@Override
public boolean isDirty() {
return false; // it's readOnly - cannot be dirty
}
@Override
public boolean isGroupCommit() {
return delegate.isGroupCommit();
}
} // class ReadOnlyJournal
/**
* Delegate pattern for {@link IIndexManager}.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
*/
private static class DelegateIndexManager implements IIndexManager {
private final IIndexManager delegate;
public DelegateIndexManager(final IIndexManager delegate) {
this.delegate = delegate;
}
@Override
public void dropIndex(String name) {
delegate.dropIndex(name);
}
@Override
public ExecutorService getExecutorService() {
return delegate.getExecutorService();
}
@Override
public BigdataFileSystem getGlobalFileSystem() {
return delegate.getGlobalFileSystem();
}
@Override
public SparseRowStore getGlobalRowStore() {
return delegate.getGlobalRowStore();
}
@Override
public SparseRowStore getGlobalRowStore(final long timestamp) {
return delegate.getGlobalRowStore(timestamp);
}
@Override
public IIndex getIndex(String name, long timestamp) {
return delegate.getIndex(name, timestamp);
}
@Override
public long getLastCommitTime() {
return delegate.getLastCommitTime();
}
@Override
public IResourceLocator<?> getResourceLocator() {
return delegate.getResourceLocator();
}
@Override
public IResourceLockService getResourceLockService() {
return delegate.getResourceLockService();
}
@Override
public void registerIndex(IndexMetadata indexMetadata) {
delegate.registerIndex(indexMetadata);
}
@Override
public void destroy() {
delegate.destroy();
}
@Override
public TemporaryStore getTempStore() {
return delegate.getTempStore();
}
@Override
public ScheduledFuture<?> addScheduledTask(Runnable task,
long initialDelay, long delay, TimeUnit unit) {
return delegate.addScheduledTask(task, initialDelay, delay, unit);
}
@Override
public boolean getCollectPlatformStatistics() {
return delegate.getCollectPlatformStatistics();
}
@Override
public boolean getCollectQueueStatistics() {
return delegate.getCollectQueueStatistics();
}
@Override
public int getHttpdPort() {
return delegate.getHttpdPort();
}
@Override
public Iterator<String> indexNameScan(String prefix, long timestamp) {
throw new UnsupportedOperationException();
}
@Override
public CounterSet getCounters() {
return delegate.getCounters();
}
@Override
public boolean isGroupCommit() {
return delegate.isGroupCommit();
}
}// DelegateIndexClass
}