/***************************************************************************
* Copyright (C) 2011 by H-Store Project *
* Brown University *
* Massachusetts Institute of Technology *
* Yale University *
* *
* Permission is hereby granted, free of charge, to any person obtaining *
* a copy of this software and associated documentation files (the *
* "Software"), to deal in the Software without restriction, including *
* without limitation the rights to use, copy, modify, merge, publish, *
* distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to *
* the following conditions: *
* *
* The above copyright notice and this permission notice shall be *
* included in all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, *
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR *
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
* OTHER DEALINGS IN THE SOFTWARE. *
***************************************************************************/
package edu.brown.hstore.txns;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.log4j.Logger;
import org.voltdb.ParameterSet;
import org.voltdb.VoltTable;
import org.voltdb.catalog.Procedure;
import org.voltdb.catalog.Table;
import org.voltdb.exceptions.SerializableException;
import org.voltdb.types.SpeculationType;
import org.voltdb.utils.NotImplementedException;
import com.google.protobuf.ByteString;
import com.google.protobuf.RpcCallback;
import edu.brown.hstore.HStoreConstants;
import edu.brown.hstore.HStoreSite;
import edu.brown.hstore.Hstoreservice.Status;
import edu.brown.hstore.Hstoreservice.UnevictDataResponse;
import edu.brown.hstore.Hstoreservice.WorkFragment;
import edu.brown.hstore.callbacks.PartitionCountingCallback;
import edu.brown.hstore.estimators.Estimate;
import edu.brown.hstore.estimators.EstimatorState;
import edu.brown.hstore.internal.FinishTxnMessage;
import edu.brown.hstore.internal.SetDistributedTxnMessage;
import edu.brown.hstore.internal.WorkFragmentMessage;
import edu.brown.interfaces.DebugContext;
import edu.brown.logging.LoggerUtil;
import edu.brown.logging.LoggerUtil.LoggerBoolean;
import edu.brown.pools.Poolable;
import edu.brown.utils.PartitionSet;
import edu.brown.utils.StringUtil;
/**
* Base Transaction State
* @author pavlo
*/
public abstract class AbstractTransaction implements Poolable, Comparable<AbstractTransaction> {
private static final Logger LOG = Logger.getLogger(AbstractTransaction.class);
private static final LoggerBoolean debug = new LoggerBoolean();
static {
LoggerUtil.attachObserver(LOG, debug);
}
/**
* Internal state for the transaction
*/
public enum RoundState {
INITIALIZED,
STARTED,
FINISHED;
}
protected final HStoreSite hstore_site;
// ----------------------------------------------------------------------------
// GLOBAL DATA MEMBERS
// ----------------------------------------------------------------------------
/**
* Catalog object of the Procedure that this transaction is currently executing
*/
private Procedure catalog_proc;
private boolean sysproc;
private boolean readonly;
private boolean allow_early_prepare = true;
protected Long txn_id = null;
protected Long last_txn_id = null; // FOR DEBUGGING
protected long client_handle;
protected int base_partition;
protected Status status;
protected SerializableException pending_error;
/**
* StoredProcedureInvocation Input Parameters
* These are the parameters that are sent from the client
*/
protected ParameterSet parameters;
/**
* Internal flag that is set to true once to tell whomever
* that this transaction handle can be deleted.
*/
private AtomicBoolean deletable = new AtomicBoolean(false);
// ----------------------------------------------------------------------------
// ATTACHED DATA STRUCTURES
// ----------------------------------------------------------------------------
/**
* Attached inputs for the current execution round.
* This cannot be in the ExecutionState because we may have attached inputs for
* transactions that aren't running yet.
*/
private Map<Integer, List<VoltTable>> attached_inputs;
/**
* Attached ParameterSets for the current execution round
* This is so that we don't have to marshal them over to different partitions on the same HStoreSite
*/
private ParameterSet attached_parameterSets[];
/**
* Internal state information for txns that request prefetch queries
* This is only needed for distributed transactions
*/
protected PrefetchState prefetch;
// ----------------------------------------------------------------------------
// INTERNAL MESSAGE WRAPPERS
// ----------------------------------------------------------------------------
private SetDistributedTxnMessage setdtxn_task;
private FinishTxnMessage finish_task;
private WorkFragmentMessage work_task[];
// ----------------------------------------------------------------------------
// GLOBAL PREDICTIONS FLAGS
// ----------------------------------------------------------------------------
/**
* Whether this transaction will only touch one partition
*/
protected boolean predict_singlePartition = false;
/**
* Whether this txn can abort
*/
protected boolean predict_abortable = true;
/**
* Whether we predict that this txn will be read-only
*/
protected boolean predict_readOnly = false;
/**
* EstimationState Handle
*/
private EstimatorState predict_tState;
/**
* The set of partitions that we expected this partition to touch.
*/
protected PartitionSet predict_touchedPartitions;
// ----------------------------------------------------------------------------
// PER PARTITION EXECUTION FLAGS
// ----------------------------------------------------------------------------
private final boolean released[];
private final boolean prepared[];
private final boolean finished[];
protected final RoundState round_state[];
protected final int round_ctr[];
/**
* The number of times that this transaction has been restarted
*/
protected int restart_ctr = 0;
/**
* The first undo token used at each local partition
* When we abort a txn we will need to give the EE this value
*/
private final long exec_firstUndoToken[];
/**
* The last undo token used at each local partition
* When we commit a txn we will need to give the EE this value
*/
private final long exec_lastUndoToken[];
/**
* This is set to true if the transaction did some work without an undo
* buffer at a local partition. This is used to just check that we aren't
* trying to rollback changes without having used the undo log.
*/
private final boolean exec_noUndoBuffer[];
/**
* Whether this transaction has been read-only so far on a local partition
*/
protected final boolean exec_readOnly[];
/**
* Whether this Transaction has queued work that may need to be removed
* from this partition
*/
protected final boolean exec_queueWork[];
/**
* Whether this Transaction has submitted work to the EE that may need to be
* rolled back on a local partition
*/
protected final boolean exec_eeWork[];
/**
* BitSets for whether this txn has read from a particular table on each
* local partition.
* PartitionId -> TableId
*/
protected final boolean readTables[][];
/**
* BitSets for whether this txn has executed a modifying query against a particular table
* on each partition. Note that it does not actually record whether any rows were changed,
* but just we executed a query that targeted that table.
* PartitionId -> TableId
*/
protected final boolean writeTables[][];
/**
* The table that this txn needs to merge the results for in the EE
* before it starts executing
*/
protected Table anticache_table = null;
/**
* Whether this txn was speculatively executed
*/
protected SpeculationType exec_specExecType = SpeculationType.NULL;
// ----------------------------------------------------------------------------
// INITIALIZATION
// ----------------------------------------------------------------------------
/**
* Constructor
* @param executor
*/
public AbstractTransaction(HStoreSite hstore_site) {
this.hstore_site = hstore_site;
int numPartitions = hstore_site.getCatalogContext().numberOfPartitions;
this.released = new boolean[numPartitions];
this.prepared = new boolean[numPartitions];
this.finished = new boolean[numPartitions];
this.round_state = new RoundState[numPartitions];
this.round_ctr = new int[numPartitions];
this.exec_readOnly = new boolean[numPartitions];
this.exec_queueWork = new boolean[numPartitions];
this.exec_eeWork = new boolean[numPartitions];
this.exec_firstUndoToken = new long[numPartitions];
this.exec_lastUndoToken = new long[numPartitions];
this.exec_noUndoBuffer = new boolean[numPartitions];
this.readTables = new boolean[numPartitions][];
this.writeTables = new boolean[numPartitions][];
Arrays.fill(this.exec_firstUndoToken, HStoreConstants.NULL_UNDO_LOGGING_TOKEN);
Arrays.fill(this.exec_lastUndoToken, HStoreConstants.NULL_UNDO_LOGGING_TOKEN);
Arrays.fill(this.exec_readOnly, true);
Arrays.fill(this.exec_queueWork, false);
Arrays.fill(this.exec_eeWork, false);
}
/**
* Initialize this TransactionState for a new Transaction invocation
* @param txn_id
* @param client_handle
* @param base_partition
* @param parameters TODO
* @param sysproc
* @param predict_singlePartition
* @param predict_readOnly
* @param predict_abortable
* @param exec_local
* @return
*/
protected final AbstractTransaction init(Long txn_id,
long client_handle,
int base_partition,
ParameterSet parameters,
Procedure catalog_proc,
PartitionSet predict_touchedPartitions,
boolean predict_readOnly,
boolean predict_abortable,
boolean exec_local) {
assert(predict_touchedPartitions != null);
assert(predict_touchedPartitions.isEmpty() == false);
assert(catalog_proc != null) : "Unexpected null Procedure catalog handle";
this.txn_id = txn_id;
this.last_txn_id = txn_id;
this.client_handle = client_handle;
this.base_partition = base_partition;
this.parameters = parameters;
this.catalog_proc = catalog_proc;
this.sysproc = this.catalog_proc.getSystemproc();
this.readonly = this.catalog_proc.getReadonly();
// Initialize the predicted execution properties for this transaction
this.predict_touchedPartitions = predict_touchedPartitions;
this.predict_singlePartition = (this.predict_touchedPartitions.size() == 1);
this.predict_readOnly = predict_readOnly;
this.predict_abortable = predict_abortable;
return (this);
}
@Override
public final boolean isInitialized() {
return (this.txn_id != null && this.catalog_proc != null);
}
/**
* <B>Note:</B> This should never be called by anything other than the TransactionInitializer
* @param txn_id
*/
public void setTransactionId(Long txn_id) {
this.txn_id = txn_id;
}
/**
* Returns the speculation type (i.e., stall point) that this txn was executed at.
* Will be null if this transaction was not speculatively executed
* @return
*/
public SpeculationType getSpeculationType() {
return (this.exec_specExecType);
}
/**
* Should be called once the TransactionState is finished and is
* being returned to the pool
*/
@Override
public void finish() {
this.predict_singlePartition = false;
this.predict_abortable = true;
this.predict_readOnly = false;
this.predict_tState = null;
this.allow_early_prepare = true;
this.pending_error = null;
this.status = null;
this.parameters = null;
if (this.attached_inputs != null) this.attached_inputs.clear();
this.attached_parameterSets = null;
for (int partition : this.hstore_site.getLocalPartitionIds().values()) {
this.released[partition] = false;
this.prepared[partition] = false;
this.finished[partition] = false;
this.round_state[partition] = null;
this.round_ctr[partition] = 0;
this.exec_readOnly[partition] = true;
this.exec_queueWork[partition] = false;
this.exec_eeWork[partition] = false;
this.exec_firstUndoToken[partition] = HStoreConstants.NULL_UNDO_LOGGING_TOKEN;
this.exec_lastUndoToken[partition] = HStoreConstants.NULL_UNDO_LOGGING_TOKEN;
this.exec_noUndoBuffer[partition] = false;
if (this.readTables[partition] != null) Arrays.fill(this.readTables[partition], false);
if (this.writeTables[partition] != null) Arrays.fill(this.writeTables[partition], false);
} // FOR
if (debug.val)
LOG.debug(String.format("Finished txn #%d and cleaned up internal state [hashCode=%d, finished=%s]",
this.txn_id, this.hashCode(), Arrays.toString(this.finished)));
this.deletable.lazySet(false);
this.catalog_proc = null;
this.sysproc = false;
this.readonly = false;
this.base_partition = HStoreConstants.NULL_PARTITION_ID;
this.txn_id = null;
}
/**
* Return the number of times that this transaction was restarted
* @return
*/
public int getRestartCounter() {
return (this.restart_ctr);
}
/**
* Set the number of times that this transaction has been restarted
* @param val
*/
public void setRestartCounter(int val) {
this.restart_ctr = val;
}
// ----------------------------------------------------------------------------
// DATA STORAGE
// ----------------------------------------------------------------------------
/**
* Store data from mapOutput tables to reduceInput table
* reduceInput table should merge all incoming data from the mapOutput tables.
*/
public Status storeData(int partition, VoltTable vt) {
throw new NotImplementedException("Not able to store data for non-MapReduce transactions");
}
// ----------------------------------------------------------------------------
// ROUND METHODS
// ----------------------------------------------------------------------------
/**
* Must be called once before one can add new WorkFragments for this txn
* This will always clear out any pending errors
* @param undoToken
*/
public void initRound(int partition, long undoToken) {
assert(this.round_state[partition] == null || this.round_state[partition] == RoundState.FINISHED) :
String.format("Invalid state %s for ROUND #%s on partition %d for %s [hashCode=%d]",
this.round_state[partition], this.round_ctr[partition],
partition, this, this.hashCode());
// If we get to this point, then we know that nobody cares about any
// errors from the previous round, therefore we can just clear it out
this.pending_error = null;
if (this.exec_lastUndoToken[partition] == HStoreConstants.NULL_UNDO_LOGGING_TOKEN ||
undoToken != HStoreConstants.DISABLE_UNDO_LOGGING_TOKEN) {
// LAST UNDO TOKEN
this.exec_lastUndoToken[partition] = undoToken;
// FIRST UNDO TOKEN
if (this.exec_firstUndoToken[partition] == HStoreConstants.NULL_UNDO_LOGGING_TOKEN ||
this.exec_firstUndoToken[partition] == HStoreConstants.DISABLE_UNDO_LOGGING_TOKEN) {
this.exec_firstUndoToken[partition] = undoToken;
}
}
// NO UNDO LOGGING
if (undoToken == HStoreConstants.DISABLE_UNDO_LOGGING_TOKEN) {
this.exec_noUndoBuffer[partition] = true;
}
this.round_state[partition] = RoundState.INITIALIZED;
if (debug.val)
LOG.debug(String.format("%s - Initializing ROUND %d at partition %d [undoToken=%d / first=%d / last=%d]",
this, this.round_ctr[partition], partition,
undoToken, this.exec_firstUndoToken[partition],
this.exec_lastUndoToken[partition]));
}
/**
* Called once all of the WorkFragments have been submitted for this txn
* @return
*/
public void startRound(int partition) {
assert(this.round_state[partition] == RoundState.INITIALIZED) :
String.format("Invalid state %s for ROUND #%s on partition %d for %s [hashCode=%d]",
this.round_state[partition], this.round_ctr[partition],
partition, this, this.hashCode());
this.round_state[partition] = RoundState.STARTED;
if (debug.val)
LOG.debug(String.format("%s - Starting batch ROUND #%d on partition %d",
this, this.round_ctr[partition], partition));
}
/**
* When a round is over, this must be called so that we can clean up the various
* dependency tracking information that we have
*/
public void finishRound(int partition) {
assert(this.round_state[partition] == RoundState.STARTED) :
String.format("Invalid batch round state %s for %s at partition %d",
this.round_state[partition], this, partition);
if (debug.val)
LOG.debug(String.format("%s - Finishing batch ROUND #%d on partition %d",
this, this.round_ctr[partition], partition));
this.round_state[partition] = RoundState.FINISHED;
this.round_ctr[partition]++;
}
// ----------------------------------------------------------------------------
// PREDICTIONS
// ----------------------------------------------------------------------------
/**
* Return the collection of the partitions that this transaction is expected
* to need during its execution. The transaction may choose to not use all of
* these but it is not allowed to use more.
*/
public final PartitionSet getPredictTouchedPartitions() {
return (this.predict_touchedPartitions);
}
/**
* Returns true if this Transaction was originally predicted as being able to abort
*/
public final boolean isPredictAbortable() {
return (this.predict_abortable);
}
/**
* Returns true if this transaction was originally predicted as read only
*/
public final boolean isPredictReadOnly() {
return (this.predict_readOnly);
}
/**
* Returns true if this Transaction was originally predicted to be single-partitioned
*/
public final boolean isPredictSinglePartition() {
return (this.predict_singlePartition);
}
/**
* Returns the EstimatorState for this txn
* @return
*/
public EstimatorState getEstimatorState() {
return (this.predict_tState);
}
/**
* Set the EstimatorState for this txn
* @param state
*/
public final void setEstimatorState(EstimatorState state) {
assert(this.predict_tState == null) :
String.format("Trying to set the %s for %s twice",
EstimatorState.class.getSimpleName(), this);
if (debug.val)
LOG.debug(String.format("%s - Setting %s for txn",
this, state.getClass().getSimpleName()));
this.predict_tState = state;
}
/**
* Get the last TransactionEstimate produced for this transaction.
* If there is no estimate, then the return result is null.
* @return
*/
public final Estimate getLastEstimate() {
if (this.predict_tState != null) {
return (this.predict_tState.getLastEstimate());
}
return (null);
}
/**
* Returns true if this transaction was executed speculatively
*/
public boolean isSpeculative() {
return (false);
}
// ----------------------------------------------------------------------------
// EXECUTION FLAG METHODS
// ----------------------------------------------------------------------------
/**
* Mark this transaction as have performed some modification on this partition
*/
public final void markExecNotReadOnly(int partition) {
assert(this.sysproc == true || this.readonly == false);
this.exec_readOnly[partition] = false;
}
/**
* Returns true if this transaction has not executed any modifying work at this partition
*/
public final boolean isExecReadOnly(int partition) {
if (this.readonly) return (true);
return (this.exec_readOnly[partition]);
}
/**
* Returns true if this transaction executed without undo buffers at some point
*/
public final boolean isExecNoUndoBuffer(int partition) {
return (this.exec_noUndoBuffer[partition]);
}
/**
* Mark that the transaction executed queries without using undo logging
* at the given partition.
* @param partition
*/
public final void markExecNoUndoBuffer(int partition) {
this.exec_noUndoBuffer[partition] = true;
}
/**
* Returns true if this transaction's control code running at this partition
*/
public final boolean isExecLocal(int partition) {
return (this.base_partition == partition);
}
/**
* Returns true if this transaction has done something at this partition and therefore
* the PartitionExecutor needs to be told that they are finished.
* This could be either executing a query or executing the transaction's control code
*/
public final boolean needsFinish(int partition) {
boolean ret = (this.exec_readOnly[partition] == false &&
(this.round_state[partition] != null ||
this.exec_eeWork[partition] ||
this.exec_queueWork[partition])
);
// if (this.isSpeculative()) {
// LOG.info(String.format(
// "%s - Speculative Execution Partition %d => %s\n" +
// " Round State: %s\n" +
// " Exec ReadOnly: %s\n" +
// " Exec Queue: %s\n" +
// " Exec EE: %s\n",
// this, partition, ret,
// this.round_state[offset],
// this.exec_readOnly[offset],
// this.exec_queueWork[offset],
// this.exec_eeWork[offset]));
// }
// This transaction needs to be "finished" down in the EE if:
// (1) It's not read-only
// (2) It has executed at least one batch round
// (3) It has invoked work directly down in the EE
// (4) It added work that may be waiting in this partition's queue
return (ret);
}
/**
* Returns true if we believe that this transaction can be deleted
* <B>Note:</B> This will only return true once and only once for each
* transaction invocation. So don't call this unless you are able to really
* delete the transaction now. If you just want to know whether the txn has
* been marked as deletable before, then use checkDeletableFlag()
* @return
*/
public boolean isDeletable() {
// if (this.isInitialized() == false) {
// return (false);
// }
return (this.deletable.compareAndSet(false, true));
}
/**
* Returns true if this txn has already been marked for deletion
* This will not change the internal state of the txn.
*/
public final boolean checkDeletableFlag() {
return (this.deletable.get());
}
// ----------------------------------------------------------------------------
// GENERAL METHODS
// ----------------------------------------------------------------------------
@Override
public final boolean equals(Object obj) {
if (obj instanceof AbstractTransaction) {
AbstractTransaction other = (AbstractTransaction)obj;
if (this.txn_id == null) {
return (this.hashCode() != other.hashCode());
}
return (this.txn_id.equals(other.txn_id));
}
return (false);
}
@Override
public final int compareTo(AbstractTransaction o) {
if (this.txn_id == null) return (1);
else if (o.txn_id == null) return (-1);
return this.txn_id.compareTo(o.txn_id);
}
/**
* Get this state's transaction id
*/
public final Long getTransactionId() {
return this.txn_id;
}
/**
* Get this state's client_handle
*/
public final long getClientHandle() {
return this.client_handle;
}
/**
* Get the base PartitionId where this txn's Java code is executing on
*/
public final int getBasePartition() {
return this.base_partition;
}
/**
* Return the underlying procedure catalog object
* @return
*/
public final Procedure getProcedure() {
return (this.catalog_proc);
}
/**
* Return the ParameterSet that contains the procedure input
* parameters for this transaction. These are the original parameters
* that were sent from the client for this txn.
* This can be null for distributed transactions on remote partitions
*/
public ParameterSet getProcedureParameters() {
return (this.parameters);
}
/**
* Returns true if this transaction is for a system procedure
*/
public final boolean isSysProc() {
return (this.sysproc);
}
public final boolean allowEarlyPrepare() {
return (this.allow_early_prepare);
}
public final void setAllowEarlyPrepare(boolean enable) {
this.allow_early_prepare = enable;
}
// ----------------------------------------------------------------------------
// CALLBACK METHODS
// ----------------------------------------------------------------------------
/**
* Return this handle's InitCallback
*/
public abstract <T extends PartitionCountingCallback<? extends AbstractTransaction>> T getInitCallback();
/**
* Return this handle's PrepareCallback
*/
public abstract <T extends PartitionCountingCallback<? extends AbstractTransaction>> T getPrepareCallback();
/**
* Return this handle's FinishCallback
*/
public abstract <T extends PartitionCountingCallback<? extends AbstractTransaction>> T getFinishCallback();
// ----------------------------------------------------------------------------
// ERROR METHODS
// ----------------------------------------------------------------------------
/**
* Returns true if this transaction has a pending error
*/
public final boolean hasPendingError() {
return (this.pending_error != null);
}
/**
* Return the pending error for this transaction.
* This does not clear it.
*/
public final SerializableException getPendingError() {
return (this.pending_error);
}
/**
* Return the message for the pending error of this transaction.
*/
public final String getPendingErrorMessage() {
return (this.pending_error != null ? this.pending_error.getMessage() : null);
}
/**
* Set the Exception that is causing this txn to abort. It will need
* to be processed later on.
* This is a thread-safe operation. Only the first error will be stored.
* @param error
*/
public synchronized void setPendingError(SerializableException error) {
assert(error != null) : "Trying to set a null error for txn #" + this.txn_id;
if (this.pending_error == null) {
if (debug.val)
LOG.warn(String.format("%s - Got %s error for txn: %s",
this, error.getClass().getSimpleName(), error.getMessage()));
this.pending_error = error;
}
}
// ----------------------------------------------------------------------------
// INTERNAL MESSAGE WRAPPERS
// ----------------------------------------------------------------------------
public final SetDistributedTxnMessage getSetDistributedTxnMessage() {
if (this.setdtxn_task == null) {
this.setdtxn_task = new SetDistributedTxnMessage(this);
}
return (this.setdtxn_task);
}
public final FinishTxnMessage getFinishTxnMessage(Status status) {
if (this.finish_task == null) {
synchronized (this) {
if (this.finish_task == null) {
this.finish_task = new FinishTxnMessage(this, status);
}
} // SYNCH
}
this.finish_task.setStatus(status);
return (this.finish_task);
}
public final WorkFragmentMessage getWorkFragmentMessage(WorkFragment fragment) {
if (this.work_task == null) {
this.work_task = new WorkFragmentMessage[hstore_site.getCatalogContext().numberOfPartitions];
}
int partition = fragment.getPartitionId();
if (this.work_task[partition] == null) {
this.work_task[partition] = new WorkFragmentMessage(this, fragment);
} else {
this.work_task[partition].setFragment(fragment);
}
return (this.work_task[partition]);
}
/**
* Set the current Status for this transaction
* This is not thread-safe.
* @param status
*/
public final void setStatus(Status status) {
this.status = status;
}
public final Status getStatus() {
return (this.status);
}
/**
* Returns true if this transaction has been aborted.
*/
public final boolean isAborted() {
return (this.status != null && this.status != Status.OK);
}
// ----------------------------------------------------------------------------
// ANTI-CACHING
// ----------------------------------------------------------------------------
public boolean hasAntiCacheMergeTable() {
return (this.anticache_table != null);
}
public Table getAntiCacheMergeTable() {
return (this.anticache_table);
}
public void setAntiCacheMergeTable(Table catalog_tbl) {
assert(this.anticache_table == null);
this.anticache_table = catalog_tbl;
}
// ----------------------------------------------------------------------------
// TRANSACTION STATE BOOKKEEPING
// ----------------------------------------------------------------------------
/**
* Mark this txn as being released from the lock queue at the given partition.
* This means that this txn was released to that partition and will
* need to be removed with a FinishTxnMessage
* @param partition - The partition to mark this txn as "released"
*/
public final void markReleased(int partition) {
if (debug.val)
LOG.debug(String.format("%s - Marking as released on partition %d %s [hashCode=%d]",
this, partition, Arrays.toString(this.released), this.hashCode()));
// assert(this.released[partition] == false) :
// String.format("Trying to mark %s as released to partition %d twice", this, partition);
this.released[partition] = true;
}
/**
* Is this TransactionState marked as released at the given partition
* @return
*/
public final boolean isMarkedReleased(int partition) {
return (this.released[partition]);
}
/**
* Mark this txn as prepared and return the original value
* This is a thread-safe operation
* @param partition - The partition to mark this txn as "prepared"
*/
public final boolean markPrepared(int partition) {
if (debug.val)
LOG.debug(String.format("%s - Marking as prepared on partition %d %s [hashCode=%d, offset=%d]",
this, partition, Arrays.toString(this.prepared),
this.hashCode(), partition));
boolean orig = false;
synchronized (this.prepared) {
orig = this.prepared[partition];
this.prepared[partition] = true;
} // SYNCH
return (orig == false);
}
/**
* Is this TransactionState marked as prepared at the given partition
* @return
*/
public final boolean isMarkedPrepared(int partition) {
return (this.prepared[partition]);
}
/**
* Mark this txn as finished (and thus ready for clean-up)
*/
public final void markFinished(int partition) {
if (debug.val)
LOG.debug(String.format("%s - Marking as finished on partition %d " +
"[finished=%s / hashCode=%d / offset=%d]",
this, partition, Arrays.toString(this.finished),
this.hashCode(), partition));
this.finished[partition] = true;
}
/**
* Is this TransactionState marked as finished
* @return
*/
public final boolean isMarkedFinished(int partition) {
return (this.finished[partition]);
}
/**
* Should be called whenever the txn submits work to the EE
*/
public final void markQueuedWork(int partition) {
if (debug.val) LOG.debug(String.format("%s - Marking as having queued work on partition %d [exec_queueWork=%s]",
this, partition, Arrays.toString(this.exec_queueWork)));
this.exec_queueWork[partition] = true;
}
/**
* Returns true if this txn has queued work at the given partition
* @return
*/
public final boolean hasQueuedWork(int partition) {
return (this.exec_queueWork[partition]);
}
/**
* Should be called whenever the txn submits work to the EE
*/
public final void markExecutedWork(int partition) {
if (debug.val) LOG.debug(String.format("%s - Marking as having submitted to the EE on partition %d [exec_eeWork=%s]",
this, partition, Arrays.toString(this.exec_eeWork)));
this.exec_eeWork[partition] = true;
}
/**
* Returns true if this txn has submitted work to the EE that needs to be rolled back
* @return
*/
public final boolean hasExecutedWork(int partition) {
return (this.exec_eeWork[partition]);
}
// ----------------------------------------------------------------------------
// READ/WRITE TABLE TRACKING
// ----------------------------------------------------------------------------
private final int[] getMarkedTableIds(boolean bitmap[]) {
int cnt = 0;
if (bitmap != null) {
for (int i = 0; i < bitmap.length; i++) {
if (bitmap[i]) cnt++;
} // FOR
}
int ret[] = new int[cnt];
if (bitmap != null) {
cnt = 0;
for (int i = 0; i < bitmap.length; i++) {
if (bitmap[i]) {
ret[cnt++] = i;
}
} // FOR
}
return (ret);
}
/**
* Return an array of the tableIds that are marked as read by the txn at
* the given partition id.
* @param partition
* @return
*/
public final int[] getTableIdsMarkedRead(int partition) {
return (this.getMarkedTableIds(this.readTables[partition]));
}
/**
* Mark that this txn read from the Table at the given partition
* @param partition
* @param catalog_tbl
*/
public final void markTableRead(int partition, Table catalog_tbl) {
if (this.readTables[partition] == null) {
this.readTables[partition] = new boolean[hstore_site.getCatalogContext().numberOfTables + 1];
}
this.readTables[partition][catalog_tbl.getRelativeIndex()] = true;
}
/**
* Mark that this txn read from the tableIds at the given partition
* @param partition
* @param tableIds
*/
public final void markTableIdsRead(int partition, int...tableIds) {
if (this.readTables[partition] == null) {
this.readTables[partition] = new boolean[hstore_site.getCatalogContext().numberOfTables + 1];
}
for (int id : tableIds) {
this.readTables[partition][id] = true;
} // FOR
}
/**
* Returns true if this txn has executed a query that read from the Table
* at the given partition.
* @param partition
* @param catalog_tbl
* @return
*/
public final boolean isTableRead(int partition, Table catalog_tbl) {
if (this.readTables[partition] != null) {
return (this.readTables[partition][catalog_tbl.getRelativeIndex()]);
}
return (false);
}
/**
* Return an array of the tableIds that are marked as written by the txn at
* the given partition id.
* @param partition
* @return
*/
public final int[] getTableIdsMarkedWritten(int partition) {
return (this.getMarkedTableIds(this.writeTables[partition]));
}
/**
* Mark that this txn has executed a modifying query for the Table at the given partition.
* <B>Note:</B> This is just tracking that we executed a query.
* It does not necessarily mean that the query actually changed anything.
* @param partition
* @param catalog_tbl
*/
public final void markTableWritten(int partition, Table catalog_tbl) {
if (this.writeTables[partition] == null) {
this.writeTables[partition] = new boolean[hstore_site.getCatalogContext().numberOfTables + 1];
}
this.writeTables[partition][catalog_tbl.getRelativeIndex()] = true;
}
/**
* Mark that this txn has executed a modifying query for the tableIds at the given partition.
* <B>Note:</B> This is just tracking that we executed a query.
* It does not necessarily mean that the query actually changed anything.
* @param partition
* @param tableIds
*/
public final void markTableIdsWritten(int partition, int...tableIds) {
if (this.writeTables[partition] == null) {
this.writeTables[partition] = new boolean[hstore_site.getCatalogContext().numberOfTables + 1];
}
for (int id : tableIds) {
this.writeTables[partition][id] = true;
} // FOR
}
/**
* Returns true if this txn has executed a non-readonly query that read from
* the Table at the given partition.
* @param partition
* @param catalog_tbl
* @return
*/
public final boolean isTableWritten(int partition, Table catalog_tbl) {
if (this.writeTables[partition] != null) {
return (this.writeTables[partition][catalog_tbl.getRelativeIndex()]);
}
return (false);
}
/**
* Returns true if this txn executed at query that either accessed or modified
* the Table at the given partition.
* @param partition
* @param catalog_tbl
* @return
*/
public final boolean isTableReadOrWritten(int partition, Table catalog_tbl) {
int tableId = catalog_tbl.getRelativeIndex();
if (this.readTables[partition] != null && this.readTables[partition][tableId]) {
return (true);
}
if (this.writeTables[partition] != null && this.writeTables[partition][tableId]) {
return (true);
}
return (false);
}
// ----------------------------------------------------------------------------
// GLOBAL STATE TRACKING
// ----------------------------------------------------------------------------
/**
* Get the current Round that this TransactionState is in
* Used only for testing
*/
protected RoundState getCurrentRoundState(int partition) {
return (this.round_state[partition]);
}
/**
* Get the first undo token used for this transaction
* When we ABORT a txn we will need to give the EE this value
*/
public long getFirstUndoToken(int partition) {
return this.exec_firstUndoToken[partition];
}
/**
* Get the last undo token used for this transaction
* When we COMMIT a txn we will need to give the EE this value
*/
public long getLastUndoToken(int partition) {
return this.exec_lastUndoToken[partition];
}
// ----------------------------------------------------------------------------
// ATTACHED DATA FOR NORMAL WORK FRAGMENTS
// ----------------------------------------------------------------------------
public final void attachParameterSets(ParameterSet parameterSets[]) {
this.attached_parameterSets = parameterSets;
}
public final ParameterSet[] getAttachedParameterSets() {
assert(this.attached_parameterSets != null) :
String.format("The attached ParameterSets for %s is null?", this);
return (this.attached_parameterSets);
}
public final void attachInputDependency(int input_dep_id, VoltTable vt) {
if (this.attached_inputs == null) {
this.attached_inputs = new HashMap<Integer, List<VoltTable>>();
}
List<VoltTable> l = this.attached_inputs.get(input_dep_id);
if (l == null) {
l = new ArrayList<VoltTable>();
this.attached_inputs.put(input_dep_id, l);
}
l.add(vt);
}
public final Map<Integer, List<VoltTable>> getAttachedInputDependencies() {
if (this.attached_inputs == null) {
this.attached_inputs = new HashMap<Integer, List<VoltTable>>();
}
return (this.attached_inputs);
}
// ----------------------------------------------------------------------------
// PREFETCH QUERIES
// ----------------------------------------------------------------------------
/**
* Grab a PrefetchState object for this LocalTransaction
* This must be called before you can send prefetch requests on behalf of this txn
*/
public final void initializePrefetch() {
if (this.prefetch == null) {
this.prefetch = new PrefetchState(this.hstore_site);
this.prefetch.init(this);
}
}
public final PrefetchState getPrefetchState() {
return (this.prefetch);
}
/**
* Returns true if this transaction has prefetched queries
*/
public final boolean hasPrefetchQueries() {
return (this.prefetch != null);
}
/**
* Attach prefetchable WorkFragments for this transaction
* This should be invoked on the remote side of the initialization request.
* That is, it is not the transaction's base partition that is storing this information,
* it's coming from over the network
* @param fragments
* @param rawParameters
*/
public void attachPrefetchQueries(List<WorkFragment> fragments, List<ByteString> rawParameters) {
assert(this.prefetch.fragments == null) :
"Trying to attach Prefetch WorkFragments more than once!";
// Simply copy the references so we don't allocate more objects
this.prefetch.fragments = fragments;
this.prefetch.paramsRaw = rawParameters;
}
public boolean hasPrefetchParameters() {
return (this.prefetch != null && this.prefetch.params != null);
}
public void attachPrefetchParameters(ParameterSet params[]) {
assert(this.prefetch.params == null) :
String.format("Trying to attach Prefetch %s more than once for %s",
ParameterSet.class.getSimpleName(), this);
this.prefetch.params = params;
}
public final boolean hasPrefetchFragments() {
return (this.prefetch != null && this.prefetch.fragments != null);
}
public final List<WorkFragment> getPrefetchFragments() {
return (this.prefetch.fragments);
}
/**
*
* @return
*/
public final List<ByteString> getPrefetchRawParameterSets() {
return (this.prefetch.paramsRaw);
}
/**
* Retrieve the ParameterSets for the prefetch queries.
* There should be one ParameterSet array per site, which means that this
* is shared across all partitions.
* @return
*/
public final ParameterSet[] getPrefetchParameterSets() {
assert(this.prefetch.params != null);
return (this.prefetch.params);
}
/**
* Mark this txn as having executed a prefetched query at the given partition
* @param partition
*/
public final void markExecPrefetchQuery(int partition) {
assert(this.prefetch != null);
this.prefetch.partitions.add(partition);
}
// ----------------------------------------------------------------------------
// DEBUG METHODS
// ----------------------------------------------------------------------------
/**
* Get the current round counter at the given partition.
* Note that a round is different than a batch. A "batch" contains multiple queries
* that the txn wants to execute, of which their PlanFragments are broken
* up into separate execution "rounds" in the PartitionExecutor.
*/
protected int getCurrentRound(int partition) {
return (this.round_ctr[partition]);
}
@Override
public final String toString() {
String str = null;
if (this.isInitialized()) {
str = this.toStringImpl();
} else {
str = String.format("<Uninitialized-%s>", this.getClass().getSimpleName());
if (this.last_txn_id != null) str += String.format(" {LAST:%d}", this.last_txn_id);
}
// Include hashCode for debugging
// str += "/" + this.hashCode();
return (str);
}
public abstract String toStringImpl();
public abstract String debug();
@SuppressWarnings("unchecked")
protected Map<String, Object>[] getDebugMaps() {
List<Map<String, Object>> maps = new ArrayList<Map<String,Object>>();
Map<String, Object> m;
m = new LinkedHashMap<String, Object>();
m.put("Transaction Id", this.txn_id);
m.put("Procedure", this.catalog_proc);
m.put("Base Partition", this.base_partition);
m.put("Hash Code", this.hashCode());
m.put("Pending Error", this.pending_error);
m.put("Allow Early Prepare", this.allow_early_prepare);
maps.add(m);
// Predictions
m = new LinkedHashMap<String, Object>();
m.put("Predict Single-Partitioned", this.predict_singlePartition);
m.put("Predict Touched Partitions", this.getPredictTouchedPartitions());
m.put("Predict Read Only", this.isPredictReadOnly());
m.put("Predict Abortable", this.isPredictAbortable());
maps.add(m);
// Global State
m = new LinkedHashMap<String, Object>();
m.put("Marked Released", PartitionSet.toString(this.released));
m.put("Marked Prepared", PartitionSet.toString(this.prepared));
m.put("Marked Finished", PartitionSet.toString(this.finished));
m.put("Marked Deletable", this.checkDeletableFlag());
maps.add(m);
// Prefetch State
if (this.prefetch != null) {
m = new LinkedHashMap<String, Object>();
m.put("Prefetch Partitions", this.prefetch.partitions);
m.put("Prefetch Fragments", StringUtil.join("\n", this.prefetch.fragments));
m.put("Prefetch Parameters", StringUtil.join("\n", this.prefetch.params));
m.put("Prefetch Raw Parameters", StringUtil.join("\n", this.prefetch.paramsRaw));
maps.add(m);
}
// Partition Execution State
m = new LinkedHashMap<String, Object>();
m.put("Current Round State", Arrays.toString(this.round_state));
m.put("Exec Read-Only", PartitionSet.toString(this.exec_readOnly));
m.put("First UndoToken", Arrays.toString(this.exec_firstUndoToken));
m.put("Last UndoToken", Arrays.toString(this.exec_lastUndoToken));
m.put("No Undo Buffer", PartitionSet.toString(this.exec_noUndoBuffer));
m.put("# of Rounds", Arrays.toString(this.round_ctr));
m.put("Executed Work", PartitionSet.toString(this.exec_eeWork));
maps.add(m);
return ((Map<String, Object>[])maps.toArray(new Map[0]));
}
public class Debug implements DebugContext {
/**
* Clear read/write table sets
* <B>NOTE:</B> This should only be used for testing
*/
public final void clearReadWriteSets() {
for (int i = 0; i < readTables.length; i++) {
if (readTables[i] != null) Arrays.fill(readTables[i], false);
if (writeTables[i] != null) Arrays.fill(writeTables[i], false);
} // FOR
}
}
private Debug cachedDebugContext;
private RpcCallback<UnevictDataResponse> unevict_callback;
private long new_transaction_id;
public Debug getDebugContext() {
if (this.cachedDebugContext == null) {
// We don't care if we're thread-safe here...
this.cachedDebugContext = new Debug();
}
return this.cachedDebugContext;
}
public void setUnevictCallback(RpcCallback<UnevictDataResponse> done) {
this.unevict_callback = done;
}
public RpcCallback<UnevictDataResponse> getUnevictCallback() {
return this.unevict_callback;
}
public void setNewTransactionId(long newTransactionId) {
this.new_transaction_id = newTransactionId;
}
public long getNewTransactionId(){
return this.new_transaction_id;
}
}