com.bigdata.journal.JournalTransactionService Maven / Gradle / Ivy
/*
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
[email protected]
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Dec 18, 2008
*/
package com.bigdata.journal;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import com.bigdata.service.AbstractFederation;
import com.bigdata.service.AbstractHATransactionService;
import com.bigdata.service.DataService;
/**
* Implementation for a standalone journal using single-phase commits.
*
* @author Bryan Thompson
*/
abstract public class JournalTransactionService extends
AbstractHATransactionService {
private final Journal journal;
/**
* @param properties
*/
public JournalTransactionService(final Properties properties,
final Journal journal) {
super(properties);
this.journal = journal;
}
@Override
public JournalTransactionService start() {
super.start();
return this;
}
/**
* Extended to register the new tx in the
* {@link AbstractLocalTransactionManager}.
*/
@Override
protected void activateTx(final TxState state) {
super.activateTx(state);
// if (TimestampUtility.isReadWriteTx(state.tx))
{
/*
* Register transaction with the local transaction manager.
*/
new Tx(journal.getLocalTransactionManager(), journal, state.tx,
state.getReadsOnCommitTime());
}
}
@Override
protected void deactivateTx(final TxState state) {
super.deactivateTx(state);
// if (TimestampUtility.isReadWriteTx(state.tx))
{
/*
* Unregister transactions.
*/
final Tx localState = journal.getLocalTransactionManager()
.getTx(state.tx);
if (localState != null) {
journal.getLocalTransactionManager().deactivateTx(localState);
}
}
}
@Override
protected long findCommitTime(final long timestamp) {
final ICommitRecord commitRecord = journal.getCommitRecord(timestamp);
if (commitRecord == null) {
return -1L;
}
return commitRecord.getTimestamp();
}
@Override
protected long findNextCommitTime(final long commitTime) {
/*
* Note: The following code did not obtain the appropriate lock to
* access the CommitRecordIndex. It was replaced by the
* getCommitRecordStrictlyGreaterThan() call, which does take the
* necessary lock and does the same thing.
*/
// final ICommitRecord commitRecord = journal.getCommitRecordIndex()
// .findNext(commitTime);
final ICommitRecord commitRecord = journal
.getCommitRecordStrictlyGreaterThan(commitTime);
if(commitRecord == null) {
return -1L;
}
return commitRecord.getTimestamp();
}
@Override
protected void abortImpl(final TxState state) {
if(state.isReadOnly()) {
/*
* A read-only transaction.
*
* Note: We do not maintain state on the client for read-only
* transactions. The state for a read-only transaction is captured
* by its transaction identifier and by state on the transaction
* service, which maintains a read lock.
*/
state.setRunState(RunState.Aborted);
return;
}
try {
/*
* The local (client-side) state for this tx.
*/
final Tx localState = journal.getLocalTransactionManager().getTx(
state.tx);
if (localState == null) {
/*
* The client should maintain the local state of the transaction
* until the transaction service either commits or aborts the
* tx.
*/
throw new AssertionError("Local tx state not found: tx="
+ state);
}
/*
* Update the local state of the tx to indicate that it is aborted.
*
* Note: We do not need to issue an abort to the journal since
* nothing is written by the transaction on the unisolated indices
* until it has validated - and the validate/merge task is an
* unisolated write operation, so the task's write set will be
* automatically discarded if it fails.
*/
localState.lock.lock();
try {
localState.setRunState(RunState.Aborted);
} finally {
localState.lock.unlock();
}
} finally {
state.setRunState(RunState.Aborted);
}
}
@Override
protected long commitImpl(final TxState state) throws ExecutionException,
InterruptedException {
if(state.isReadOnly()) {
/*
* A read-only transaction.
*
* Note: We do not maintain state on the client for read-only
* transactions. The state for a read-only transaction is captured
* by its transaction identifier and by state on the transaction
* service, which maintains a read lock.
*/
state.setRunState(RunState.Committed);
return 0L;
}
final Tx localState = journal.getLocalTransactionManager().getTx(
state.tx);
if (localState == null) {
throw new AssertionError("Not in local tables: " + state);
}
/*
* Note: This code is shared (copy by value) by the DataService
* singlePhaseCommit.
*/
{
/*
* A transaction with an empty write set can commit immediately
* since validation and commit are basically NOPs (this is the same
* as the read-only case.)
*
* Note: We lock out other operations on this tx so that this
* decision will be atomic.
*/
localState.lock.lock();
try {
if (localState.isEmptyWriteSet()) {
/*
* Sort of a NOP commit.
*/
localState.setRunState(RunState.Committed);
journal.getLocalTransactionManager().deactivateTx(
localState);
state.setRunState(RunState.Committed);
return 0L;
}
} finally {
localState.lock.unlock();
}
}
final IConcurrencyManager concurrencyManager = journal.getConcurrencyManager();
final AbstractTask task = new SinglePhaseCommit(
concurrencyManager, journal.getLocalTransactionManager(),
localState);
try {
/*
* Submit the task and wait for the result.
*
* Note: This task MUST go through the ConcurrencyManager to obtain
* its locks.
*/
concurrencyManager./* getWriteService(). */submit(task).get();
/*
* FIXME The state changes for the local tx should be atomic across
* this operation. In order to do that we have to make those changes
* inside of SinglePhaseCommit while it is holding the lock, but after
* it has committed. Perhaps the best way to do this is with a pre-
* and post- call() API since we can not hold the lock across the
* task otherwise (it will deadlock).
*/
localState.lock.lock();
try {
localState.setRunState(RunState.Committed);
journal.getLocalTransactionManager().deactivateTx(localState);
state.setRunState(RunState.Committed);
} finally {
localState.lock.unlock();
}
} catch (Throwable t) {
// log.error(t.getMessage(), t);
localState.lock.lock();
try {
localState.setRunState(RunState.Aborted);
journal.getLocalTransactionManager().deactivateTx(localState);
state.setRunState(RunState.Aborted);
throw new RuntimeException(t);
} finally {
localState.lock.unlock();
}
}
/*
* Note: This is returning the commitTime set on the task when it was
* committed as part of a group commit.
*/
// log.warn("\n" + state + "\n" + localState);
return task.getCommitTime();
}
/**
* This task is an UNISOLATED operation that validates and commits a
* transaction known to have non-empty write sets.
*
* Note: DO NOT {@link Tx#lock} while you submit this task as it
* could cause a deadlock if there is a task ahead of you in the queue for
* the same tx!
*
* Note: DO NOT use this task for a distributed transaction (one with writes
* on more than one {@link DataService}) since it will fail to obtain a
* coherent commit time for the transaction as a whole.
*
* @author Bryan Thompson
*/
public static class SinglePhaseCommit extends AbstractTask {
/**
* The transaction that is being committed.
*/
private final Tx state;
private final ILocalTransactionManager localTransactionManager;
public SinglePhaseCommit(final IConcurrencyManager concurrencyManager,
final ILocalTransactionManager localTransactionManager,
final Tx state) {
super(concurrencyManager, ITx.UNISOLATED, state.getDirtyResource());
if (localTransactionManager == null)
throw new IllegalArgumentException();
this.localTransactionManager = localTransactionManager;
this.state = state;
}
@Override
public Void doTask() throws Exception {
/*
* Note: In this case the [revisionTime] will be LT the
* [commitTime]. That's Ok as long as the issued revision times are
* strictly increasing, which they are.
*/
final long revisionTime = localTransactionManager.nextTimestamp();
/*
* Lock out other operations on this tx.
*/
state.lock.lockInterruptibly();
try {
// Note: throws ValidationError.
state.prepare(revisionTime);
} finally {
state.lock.unlock();
}
return null;
}
}
/**
* This task is an UNISOLATED operation that validates a transaction known to
* have non-empty write sets.
*
* Note: DO NOT {@link Tx#lock} while you submit this task as it could cause
* a deadlock if there is a task ahead of you in the queue for the same tx!
*
* Note: DO NOT use this task for a distributed transaction (one with writes
* on more than one {@link DataService}) since it will fail to obtain a
* coherent commit time for the transaction as a whole.
*
* @author Bryan
* Thompson
*/
public static class ValidateWriteSetTask extends AbstractTask {
/**
* The transaction that is being validated.
*/
private final Tx state;
public ValidateWriteSetTask(final IConcurrencyManager concurrencyManager,
final ILocalTransactionManager localTransactionManager,
final Tx state) {
super(concurrencyManager, ITx.UNISOLATED, state.getDirtyResource());
if (localTransactionManager == null)
throw new IllegalArgumentException();
this.state = state;
}
@Override
public Boolean doTask() throws Exception {
/*
* Lock out other operations on this tx.
*/
state.lock.lockInterruptibly();
try {
// Note: throws ValidationError.
return state.validateWriteSets();
} finally {
state.lock.unlock();
}
}
}
/**
* The last commit time from the current root block.
*/
@Override
final public long getLastCommitTime() {
return journal.getRootBlockView().getLastCommitTime();
}
/* @todo This is only true for the WORM. For the RWStore, the release time
* will advance normally and things can get aged out of the store.
*/
// /**
// * Ignored since the {@link Journal} records the last commit time
// * in its root blocks.
// */
// public void notifyCommit(long commitTime) {
//
// // NOP
//
// }
/* @todo This is only true for the WORM. For the RWStore, the release time
* will advance normally and things can get aged out of the store.
*/
// /**
// * Always returns ZERO (0L) since history can not be released on the
// * {@link Journal}.
// */
// @Override
// public long getReleaseTime() {
//
// return 0L;
//
// }
// /**
// * Throws exception since distributed transactions are not used for a single
// * {@link Journal}.
// */
// @Override
// public long prepared(long tx, UUID dataService) throws IOException {
//
// throw new UnsupportedOperationException();
//
// }
//
// /**
// * Throws exception since distributed transactions are not used for a single
// * {@link Journal}.
// */
// @Override
// public boolean committed(long tx, UUID dataService) throws IOException {
//
// throw new UnsupportedOperationException();
//
// }
/**
* Throws exception.
*
* @throws UnsupportedOperationException
* always.
*/
@Override
public AbstractFederation> getFederation() {
throw new UnsupportedOperationException();
}
// /**
// * Invoke a method with the {@link AbstractTransactionService}'s lock held.
// *
// * @param
// * @param callable
// * @return
// * @throws Exception
// */
// public T callWithLock(final Callable callable) throws Exception {
// lock.lock();
// try {
// return callable.call();
// } finally {
// lock.unlock();
// }
// }
//
// /**
// * Invoke a method with the {@link AbstractTransactionService}'s lock held.
// *
// * But throw immediate exception if try fails.
// *
// * @param
// * @param callable
// * @return
// * @throws Exception
// */
// public T tryCallWithLock(final Callable callable, long waitFor, TimeUnit unit) throws Exception {
// if (!lock.tryLock(waitFor,unit)) {
// throw new RuntimeException("Lock not available");
// }
// try {
// return callable.call();
// } finally {
// lock.unlock();
// }
// }
}