/*
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
*/
package com.bigdata.rdf.task;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicReference;
import org.openrdf.repository.RepositoryException;
import org.openrdf.sail.SailException;
import com.bigdata.counters.CAT;
import com.bigdata.journal.IConcurrencyManager;
import com.bigdata.journal.IIndexManager;
import com.bigdata.journal.IReadOnly;
import com.bigdata.journal.ITx;
import com.bigdata.journal.Journal;
import com.bigdata.journal.TimestampUtility;
import com.bigdata.rdf.changesets.IChangeLog;
import com.bigdata.rdf.changesets.IChangeRecord;
import com.bigdata.rdf.sail.BigdataSail;
import com.bigdata.rdf.sail.BigdataSail.BigdataSailConnection;
import com.bigdata.rdf.sail.BigdataSailRepository;
import com.bigdata.rdf.sail.BigdataSailRepositoryConnection;
import com.bigdata.rdf.sail.webapp.DatasetNotFoundException;
import com.bigdata.resources.IndexManager;
import com.bigdata.service.IBigdataFederation;
import com.bigdata.sparse.GlobalRowStoreHelper;
/**
* Base class for task-oriented concurrency. Directly derived classes are
* suitable for internal tasks (stored queries, stored procedures, etc) while
* REST API tasks are based on a specialized subclass that also provides for
* access to the HTTP request and response.
*
* <strong>CAUTION: Instances of this class that perform mutations MUST throw an
* exception if they do not want to join a commit group. Failure to follow this
* guideline can break the ACID contract.</strong>
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @see <a href="http://trac.blazegraph.com/ticket/566" > Concurrent unisolated
* operations against multiple KBs </a>
*/
abstract public class AbstractApiTask<T> implements IApiTask<T>, IReadOnly {
/** The reference to the {@link IIndexManager} is set before the task is executed. */
private final AtomicReference<IIndexManager> indexManagerRef = new AtomicReference<IIndexManager>();
/** The namespace of the target KB instance. */
protected final String namespace;
/** The timestamp of the view of that KB instance. */
protected final long timestamp;
/**
* The GRS is required for create/destroy of a relation (triple/quad store,
* etc).
*/
private final boolean isGRSRequired;
private final CAT mutationCount = new CAT();
@Override
abstract public boolean isReadOnly();
@Override
final public String getNamespace() {
return namespace;
}
@Override
final public long getTimestamp() {
return timestamp;
}
@Override
public boolean isGRSRequired() {
return isGRSRequired;
}
@Override
public String toString() {
return getClass().getName() + "{namespace=" + getNamespace()
+ ",timestamp=" + getTimestamp() + ", isGRSRequired="
+ isGRSRequired + "}";
}
/**
* @param namespace
* The namespace of the target KB instance.
* @param timestamp
* The timestamp of the view of that KB instance.
*/
protected AbstractApiTask(final String namespace, final long timestamp) {
this(namespace, timestamp, false/* requiresGRS */);
}
/**
*
* @param namespace
* The namespace of the target KB instance.
* @param timestamp
* The timestamp of the view of that KB instance.
* @param isGRSRequired
* True iff a lock must be obtain on the Global Row Store (GRS).
* For example, the GRS is required for create/destroy of a
* relation (triple/quad store, etc).
*/
protected AbstractApiTask(final String namespace,
final long timestamp, final boolean isGRSRequired) {
this.namespace = namespace;
this.timestamp = timestamp;
this.isGRSRequired = isGRSRequired;
}
@Override
public void setIndexManager(final IIndexManager indexManager) {
indexManagerRef.set(indexManager);
}
@Override
public IIndexManager getIndexManager() {
final IIndexManager tmp = indexManagerRef.get();
if (tmp == null)
throw new IllegalStateException();
return tmp;
}
// /**
// * Return a view of the {@link AbstractTripleStore} for the namespace and
// * timestamp associated with this task.
// *
// * @return The {@link AbstractTripleStore} -or- <code>null</code> if none is
// * found for that namespace and timestamp.
// */
// protected AbstractTripleStore getTripleStore() {
//
// return getTripleStore(namespace, timestamp);
//
// }
//
// /**
// * Return a view of the {@link AbstractTripleStore} for the given namespace
// * that will read on the commit point associated with the given timestamp.
// *
// * @param namespace
// * The namespace.
// * @param timestamp
// * The timestamp or {@link ITx#UNISOLATED} to obtain a read/write
// * view of the index.
// *
// * @return The {@link AbstractTripleStore} -or- <code>null</code> if none is
// * found for that namespace and timestamp.
// */
// protected AbstractTripleStore getTripleStore(final String namespace,
// final long timestamp) {
//
// // resolve the default namespace.
// final AbstractTripleStore tripleStore = (AbstractTripleStore) getIndexManager()
// .getResourceLocator().locate(namespace, timestamp);
//
// return tripleStore;
//
// }
/**
* Return a connection transaction, which may be either read-only or support
* mutation depending on the timestamp associated with the task's view. When
* the timestamp is associated with a historical commit point, this will be
* a read-only connection. When it is associated with the
* {@link ITx#UNISOLATED} view or a read-write transaction, this will be a
* mutable connection.
* <p>
* This version uses the namespace and timestamp associated with the HTTP
* request.
*
* @throws RepositoryException
* @throws DatasetNotFoundException
*/
protected BigdataSailRepositoryConnection getQueryConnection()
throws RepositoryException {
/*
* Note: [timestamp] will be a read-only tx view of the triple store if
* a READ_LOCK was specified when the NanoSparqlServer was started
* (unless the query explicitly overrides the timestamp of the view on
* which it will operate).
*/
return getQueryConnection(namespace, timestamp);
}
/**
* This version uses the namespace and timestamp provided by the caller.
*
* @param namespace
* @param timestamp
* @return
* @throws RepositoryException
* @throws DatasetNotFoundException
*/
protected BigdataSailRepositoryConnection getQueryConnection(
final String namespace, final long timestamp)
throws RepositoryException {
// Wrap with SAIL.
final BigdataSail sail = new BigdataSail(namespace, getIndexManager());
final BigdataSailRepository repo = new BigdataSailRepository(sail);
repo.initialize();
if (TimestampUtility.isReadOnly(timestamp)) {
return (BigdataSailRepositoryConnection) repo.getReadOnlyConnection(timestamp);
}
// Read-write connection.
final BigdataSailRepositoryConnection conn = repo.getConnection();
conn.setAutoCommit(false);
return conn;
}
protected BigdataSailConnection getUnisolatedSailConnection() throws SailException, InterruptedException {
// Wrap with SAIL.
final BigdataSail sail = new BigdataSail(namespace, getIndexManager());
sail.initialize();
final BigdataSailConnection conn = sail.getUnisolatedConnection();
// Setup a change listener. It will notice the #of mutations.
conn.addChangeLog(new SailChangeLog());
return conn;
}
/**
* Return a connection for the namespace. If the task is associated with
* either a read/write transaction or an {@link ITx#UNISOLATED} view of the
* indices, the connection may be used to read or write on the namespace.
* Otherwise the connection will be read-only.
*
* @return The connection.
*
* @throws SailException
* @throws RepositoryException
* @throws DatasetNotFoundException
* if the specified namespace does not exist.
*/
protected BigdataSailRepositoryConnection getConnection()
throws SailException, RepositoryException {
// Wrap with SAIL.
final BigdataSail sail = new BigdataSail(namespace, getIndexManager());
final BigdataSailRepository repo = new BigdataSailRepository(sail);
repo.initialize();
final BigdataSailRepositoryConnection conn = repo.getConnection();
conn.setAutoCommit(false);
// Setup a change listener. It will notice the #of mutations.
conn.addChangeLog(new SailChangeLog());
return conn;
}
private class SailChangeLog implements IChangeLog {
@Override
public final void changeEvent(final IChangeRecord record) {
mutationCount.increment();
}
@Override
public void transactionBegin() {
}
@Override
public void transactionPrepare() {
}
@Override
public void transactionCommited(long commitTime) {
}
@Override
public void transactionAborted() {
}
@Override
public void close() {
}
}
/**
* Submit a task and return a {@link Future} for that task. The task will be
* run on the appropriate executor service depending on the nature of the
* backing database and the view required by the task.
* <p>
* <strong> This method returns a {@link Future}. Remember to do
* {@link Future#get()} on the returned {@link Future} to await the group
* commit.</strong>
*
* @param indexManager
* The {@link IndexManager}.
* @param task
* The task.
*
* @return The {@link Future} for that task.
*
* @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>
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
static public <T> FutureTask<T> submitApiTask(final IIndexManager indexManager,
final IApiTask<T> task) {
final String namespace = task.getNamespace();
final long timestamp = task.getTimestamp();
if (!indexManager.isGroupCommit()
|| indexManager instanceof IBigdataFederation
|| TimestampUtility.isReadOnly(timestamp)) {
/*
* Execute the REST API task.
*
* Note: For scale-out, the operation will be applied using client-side
* global views of the indices. This means that there will not be any
* globally consistent views of the indices and that updates will be
* shard-wise local (even through scale-out uses group commit, we do
* not submit tasks on the client via the group commit API).
*
* Note: This can be used for operations on read-only views (even on a
* Journal). This is helpful since we can avoid some overhead
* associated the AbstractTask lock declarations and the overhead
* associated with an isolated TemporaryStore per read-only or
* read-write tx AbstractTask instance.
*/
// Wrap Callable.
final FutureTask<T> ft = new FutureTask<T>(new ApiTaskForIndexManager(
indexManager, task));
// /*
// * Caller runs (synchronous execution)
// *
// * Note: By having the caller run the task here we avoid consuming
// * another thread.
// */
// ft.run();
/*
* Submit to an executor.
*
* Note: The code was changed to submit to an executor so the caller
* does not block while inside of submitApiTask(). This makes it
* possible to support the StatusServlet's ability to list the
* running tasks.
*
* @see <a href="http://trac.bigdata.com/ticket/1254" > All REST API
* operations should be cancelable from both REST API and workbench
* </a>
*/
indexManager.getExecutorService().execute(ft);
return ft;
} else {
/**
* Run on the ConcurrencyManager of the Journal.
*
* Mutation operations will be scheduled based on the pre-declared
* locks and will have exclusive access to the resources guarded by
* those locks when they run.
*
* FIXME GIST: The hierarchical locking mechanisms will fail on durable
* named solution sets because they use either HTree or Stream and
* AbstractTask does not yet support those durable data structures (it
* is still being refactored to support the ICheckpointProtocol rather
* than the BTree in its Name2Addr isolation logic).
*/
// Obtain the names of the necessary locks for R/W access to indices.
final String[] locks = getLocksForKB((Journal) indexManager,
namespace, task.isGRSRequired());
final IConcurrencyManager cc = ((Journal) indexManager)
.getConcurrencyManager();
/*
* Submit task to ConcurrencyManager.
*
* Task will (eventually) acquire locks and run.
*
* Note: The Future of that task is returned to the caller.
*
* Note: ConcurrencyManager.submit() requires an AbstractTask. This
* makes it quite difficult for us to return a FutureTask here. Making
* the change there touches the lock manager and write executor service
* but maybe it should be done since it is otherwise difficult to
* convert a Future into a FutureTask or RunnableFuture.
*
* TODO Could pass through timeout for submitted task here.
*/
final FutureTask<T> ft = cc.submit(new ApiTaskForJournal(cc, task
.getTimestamp(), locks, task));
return ft;
}
}
/**
* Return the set of locks that the task must acquire in order to operate on
* the specified namespace.
*
* @param indexManager
* The {@link Journal}.
* @param namespace
* The namespace of the KB instance.
* @param requiresGRS
* GRS is required for create/destroy of a relation (triple/quad
* store, etc).
*
* @return The locks for the named indices associated with that KB instance.
*/
private static String[] getLocksForKB(final Journal indexManager,
final String namespace, final boolean requiresGRS) {
/*
* This uses hierarchical locking, so it just returns the namespace. This
* is implicitly used to contend with any other unisolated operations on
* the same namespace. Thus we do not need to enumerate the indices under
* that namespace.
*/
if (requiresGRS) {
/*
* The GRS is required for create/destroy of a relation (triple/quad store, etc).
*/
return new String[] { GlobalRowStoreHelper.GLOBAL_ROW_STORE_INDEX,
namespace };
} else {
return new String[] { namespace };
}
}
public long getMutationCount() {
return this.mutationCount.get();
}
}