package edu.brown.hstore.txns;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
import org.voltdb.CatalogContext;
import org.voltdb.VoltTable;
import edu.brown.hstore.Hstoreservice.WorkFragment;
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;
/**
* A container for a single output dependency generated by a PlanFragment
* This can handle results from multiple partitions for the same fragment.
* @author pavlo
*/
public class DependencyInfo implements Poolable {
private static final Logger LOG = Logger.getLogger(DependencyInfo.class);
private static final LoggerBoolean debug = new LoggerBoolean();
private static final LoggerBoolean trace = new LoggerBoolean();
static {
LoggerUtil.attachObserver(LOG, debug, trace);
}
// ----------------------------------------------------------------------------
// INVOCATION DATA MEMBERS
// ----------------------------------------------------------------------------
private Long txn_id;
private int round;
private int stmt_counter = -1;
private int stmt_index = -1;
private int dependency_id = -1;
private int params_hash = -1;
/**
* List of PartitionIds that we expect to get responses/results back
*/
private final PartitionSet expectedPartitions;
/**
* The list of VoltTable results that have been sent back from partitions
* We store it as a list so that we don't have to convert it for ExecutionSite
*/
private final List<VoltTable> results = new ArrayList<VoltTable>();
/**
* The List of PartitionIds that we have successfully gotten back from partitions
*/
private final PartitionSet resultPartitions;
/**
* We assume a 1-to-n mapping from DependencyInfos to blocked FragmentTaskMessages
*/
private final Set<WorkFragment.Builder> blockedTasks = new HashSet<WorkFragment.Builder>();
/**
* If set to true, that means we have already released all the tasks that were
* blocked on the results generated for this dependency
*/
private boolean blockedTasksReleased = false;
/**
* Is the data for this dependency for intermediate results that
* are only sent to another WorkFragment (as opposed to being sent back
* to the transaction's control code).
*/
private boolean internal = false;
/**
* Is the data for this dependency for a prefetched query. If this is
* set to true, then this
*/
private boolean prefetch = false;
// ----------------------------------------------------------------------------
// INITIALIZATION
// ----------------------------------------------------------------------------
/**
* Constructor
*/
protected DependencyInfo(CatalogContext catalogContext) {
this.expectedPartitions = new PartitionSet(); // catalogContext.numberOfPartitions);
this.resultPartitions = new PartitionSet(); // catalogContext.numberOfPartitions);
}
public void init(Long txn_id, int round, int stmt_counter, int stmt_index, int params_hash, int dependency_id) {
if (debug.val)
LOG.debug(String.format("#%s - Initializing DependencyInfo for %s in ROUND #%d",
txn_id, TransactionUtil.debugStmtDep(stmt_counter, dependency_id), round));
this.txn_id = txn_id;
this.round = round;
this.stmt_counter = stmt_counter;
this.stmt_index = stmt_index;
this.params_hash = params_hash;
this.dependency_id = dependency_id;
}
@Override
public boolean isInitialized() {
return (this.txn_id != null);
}
@Override
public void finish() {
this.txn_id = null;
this.stmt_counter = -1;
this.stmt_index = -1;
this.dependency_id = -1;
this.params_hash = -1;
this.expectedPartitions.clear();
this.blockedTasks.clear();
this.blockedTasksReleased = false;
this.internal = false;
this.prefetch = false;
this.results.clear();
this.resultPartitions.clear();
}
/**
* Special method for overriding this DependencyInfo's current round
* and output dependency id. This is needed for prefetched WorkFragments
* that don't have the real id when they were original created.
* @param round
* @param dependency_id
* @param stmt_index
*/
protected void prefetchOverride(int round, int dependency_id, int stmt_index) {
this.round = round;
this.dependency_id = dependency_id;
this.stmt_index = stmt_index;
}
// ----------------------------------------------------------------------------
// ACCESS METHODS
// ----------------------------------------------------------------------------
public Long getTransactionId() {
return (this.txn_id);
}
protected int getRound() {
return (this.round);
}
public int getStatementCounter() {
return (this.stmt_counter);
}
public int getStatementIndex() {
return (this.stmt_index);
}
public int getParameterSetHash() {
return (this.params_hash);
}
public int getDependencyId() {
return (this.dependency_id);
}
protected boolean inSameTxnRound(Long txn_id, int round) {
return (txn_id.equals(this.txn_id) && this.round == round);
}
protected void markInternal() {
if (debug.val)
LOG.debug(String.format("#%s - Marking DependencyInfo for %s as internal",
this.txn_id, TransactionUtil.debugStmtDep(this.stmt_counter, this.dependency_id)));
this.internal = true;
}
public boolean isInternal() {
return (this.internal);
}
public void markPrefetch() {
this.prefetch = true;
}
public void resetPrefetch() {
this.prefetch = false;
}
public boolean isPrefetch() {
return (this.prefetch);
}
// ----------------------------------------------------------------------------
// API METHODS
// ----------------------------------------------------------------------------
/**
* Add a FragmentTaskMessage this blocked until all of the partitions return results/responses
* for this DependencyInfo
* @param ftask
*/
public void addBlockedWorkFragment(WorkFragment.Builder ftask) {
if (trace.val) LOG.trace("Adding block FragmentTaskMessage for txn #" + this.txn_id);
this.blockedTasks.add(ftask);
}
/**
* Return the set of FragmentTaskMessages that are blocked until all of the partitions
* return results/responses for this DependencyInfo
* @return
*/
protected Collection<WorkFragment.Builder> getBlockedWorkFragments() {
return (this.blockedTasks);
}
/**
* Gets the blocked tasks for this DependencyInfo and marks them as "released"
* If the tasks have already been released, then the return value will be null;
* @return
*/
public Collection<WorkFragment.Builder> getAndReleaseBlockedWorkFragments() {
if (this.blockedTasksReleased == false) {
this.blockedTasksReleased = true;
if (trace.val)
LOG.trace(String.format("Unblocking %d FragmentTaskMessages for txn #%d",
this.blockedTasks.size(), this.txn_id));
return (this.blockedTasks);
}
if (trace.val)
LOG.trace(String.format("Ignoring duplicate release request for txn #%d", this.txn_id));
return (null);
}
/**
* Add a partition id that we expect to return a result/response for this dependency
* @param partition
*/
public void addPartition(int partition) {
this.expectedPartitions.add(partition);
}
/**
* <B>NOTE:</B> This should only be called for DEBUG purposes only
*/
protected int getPartitionCount() {
return (this.expectedPartitions.size());
}
/**
* <B>NOTE:</B> This should only be called for DEBUG purposes only
*/
protected PartitionSet getExpectedPartitions() {
return (this.expectedPartitions);
}
/**
* Add a result for a PartitionId.
* Returns true if we have all of the results that we expected to get
* from all of the partitions.
* @param partition
* @param result
* @return
*/
public boolean addResult(int partition, VoltTable result) {
if (debug.val)
LOG.debug(String.format("#%s - Storing RESULT for DependencyId #%d from partition %02d with %d tuples",
this.txn_id, this.dependency_id, partition, result.getRowCount()));
assert(this.resultPartitions.contains(partition) == false) :
String.format("Trying to add result %s into %s twice for %s!\n%s",
TransactionUtil.debugPartDep(partition, this.dependency_id),
this, this.txn_id, this.debug());
assert(this.expectedPartitions.contains(partition)) :
String.format("Unexpected partition result %s for %s!\n%s",
TransactionUtil.debugPartDep(partition, this.dependency_id),
this.txn_id, this.debug());
this.results.add(result);
this.resultPartitions.add(partition);
return (this.expectedPartitions.size() == this.resultPartitions.size());
}
/**
* Get the number of results that have arrived so far for this DependencyInfo
* @return
*/
protected int getResultsCount() {
return (this.resultPartitions.size());
}
protected List<VoltTable> getResults() {
return (this.results);
}
/**
* Returns true if this DependencyInfo has all of the results
* from the partitions that it was expected to get results from.
* @return
*/
protected boolean hasAllResults() {
return (this.expectedPartitions.size() == this.resultPartitions.size());
}
/**
* Return just the first result for this DependencyInfo
* This should only be called to get back the results for the final VoltTable of a query
* @return
*/
public VoltTable getResult() {
assert(this.resultPartitions.size() > 0) :
String.format("There are no results available for %s\n%s", this, this.debug());
assert(this.resultPartitions.size() == 1) :
String.format("There are % results for %s\n" +
"-------\n%s\n" +
"-------\n%s",
this.resultPartitions.size(), this, this.results, this.debug());
return (this.results.get(0));
}
/**
* Returns true if the task blocked by this Dependency is now ready to run
* @return
*/
public boolean hasTasksReady() {
if (debug.val)
LOG.debug(String.format("txn #%d - hasTasksReady()\n" +
"Block Tasks Not Empty? %s\n" +
"# of Results: %d\n" +
"# of Partitions: %d",
this.txn_id,
this.blockedTasks.isEmpty() == false,
this.resultPartitions.size(),
this.expectedPartitions.size()));
assert(this.resultPartitions.size() <= this.expectedPartitions.size()) :
String.format("Invalid DependencyInfo state for txn #%d. " +
"There are %d results but %d partitions",
this.txn_id, this.resultPartitions.size(), this.expectedPartitions.size());
return (this.blockedTasks.isEmpty() == false) &&
(this.blockedTasksReleased == false) &&
(this.resultPartitions.size() == this.expectedPartitions.size());
}
public boolean hasTasksBlocked() {
return (this.blockedTasks.isEmpty() == false);
}
public boolean hasTasksReleased() {
return (this.blockedTasksReleased);
}
// ----------------------------------------------------------------------------
// DEBUG METHODS
// ----------------------------------------------------------------------------
@Override
public String toString() {
return String.format("DependencyInfo[#%d/hashCode:%d]",
this.dependency_id, this.hashCode());
}
public String debug() {
if (this.isInitialized() == false) {
return ("<UNINITIALIZED>");
}
String status = null;
if (this.resultPartitions.size() == this.expectedPartitions.size()) {
if (this.blockedTasksReleased == false) {
status = "READY";
} else {
status = "RELEASED";
}
} else if (this.blockedTasks.isEmpty()) {
status = "WAITING";
} else {
status = "BLOCKED";
}
Map<String, Object> m = new LinkedHashMap<String, Object>();
m.put("- Internal", this.internal);
m.put("- Prefetch", this.prefetch);
m.put("- Round", this.round);
m.put("- Stmt Counter", this.stmt_counter);
m.put("- Stmt Index", this.stmt_index);
m.put("- Parameters Hash", this.params_hash);
m.put("- Expected Partitions", this.expectedPartitions);
m.put("- Result Partitions", this.resultPartitions);
Map<String, Object> inner = new LinkedHashMap<String, Object>();
for (int i = 0; i < this.results.size(); i++) {
VoltTable vt = this.results.get(i);
inner.put(String.format("#%02d", i),
String.format("{%d tuples}", vt.getRowCount()));
} // FOR
m.put("- Results", inner);
m.put("- Blocked", this.blockedTasks);
m.put("- Status", status);
return (this.toString() + "\n" + StringUtil.formatMaps(m).trim());
}
}