package edu.brown.hstore.specexec.checkers;
import java.util.Arrays;
import org.apache.log4j.Logger;
import org.voltdb.CatalogContext;
import org.voltdb.catalog.ConflictPair;
import org.voltdb.catalog.ConflictSet;
import org.voltdb.catalog.Procedure;
import org.voltdb.catalog.TableRef;
import edu.brown.catalog.conflicts.ConflictSetUtil;
import edu.brown.hstore.txns.AbstractTransaction;
import edu.brown.hstore.txns.LocalTransaction;
import edu.brown.logging.LoggerUtil;
import edu.brown.logging.LoggerUtil.LoggerBoolean;
public class TableConflictChecker extends AbstractConflictChecker {
private static final Logger LOG = Logger.getLogger(TableConflictChecker.class);
private static final LoggerBoolean debug = new LoggerBoolean();
private static final LoggerBoolean trace = new LoggerBoolean();
static {
LoggerUtil.attachObserver(LOG, debug, trace);
}
private final boolean hasConflicts[];
private final boolean rwConflicts[][];
private final boolean wwConflicts[][];
public TableConflictChecker(CatalogContext catalogContext) {
super(catalogContext);
int size = this.catalogContext.procedures.size()+1;
this.hasConflicts = new boolean[size];
this.rwConflicts = new boolean[size][size];
this.wwConflicts = new boolean[size][size];
Arrays.fill(this.hasConflicts, false);
for (Procedure catalog_proc : this.catalogContext.procedures) {
if (catalog_proc.getSystemproc() || catalog_proc.getMapreduce()) continue;
// Precompute bitmaps for the conflicts
int idx = catalog_proc.getId();
this.rwConflicts[idx] = new boolean[size];
for (Procedure conflict : ConflictSetUtil.getReadWriteConflicts(catalog_proc)) {
this.rwConflicts[idx][conflict.getId()] = true;
this.hasConflicts[idx] = true;
} // FOR
this.wwConflicts[idx] = new boolean[size];
for (Procedure conflict : ConflictSetUtil.getWriteWriteConflicts(catalog_proc)) {
this.wwConflicts[idx][conflict.getId()] = true;
this.hasConflicts[idx] = true;
} // FOR
// XXX: Each procedure will conflict with itself if it's not read-only
if (catalog_proc.getReadonly() == false) {
this.rwConflicts[idx][idx] = true;
this.wwConflicts[idx][idx] = true;
this.hasConflicts[idx] = true;
}
} // FOR
}
@Override
public boolean shouldIgnoreTransaction(AbstractTransaction ts) {
return (this.hasConflicts[ts.getProcedure().getId()] == false);
}
@Override
public boolean hasConflictBefore(AbstractTransaction dtxn, LocalTransaction candidate, int partitionId) {
assert(dtxn.isInitialized()) :
String.format("Uninitialized distributed transaction handle [%s]", dtxn);
assert(candidate.isInitialized()) :
String.format("Uninitialized speculative transaction handle [%s]", candidate);
final Procedure dtxn_proc = dtxn.getProcedure();
final Procedure ts_proc = candidate.getProcedure();
final int dtxn_procId = dtxn_proc.getId();
final int ts_procId = ts_proc.getId();
// DTXN->TS
boolean dtxn_hasRWConflict = this.rwConflicts[dtxn_procId][ts_procId];
boolean dtxn_hasWWConflict = this.wwConflicts[dtxn_procId][ts_procId];
if (debug.val)
LOG.debug(String.format("%s -> %s [R-W:%s / W-W:%s]",
dtxn, candidate, dtxn_hasRWConflict, dtxn_hasWWConflict));
// TS->DTXN
boolean ts_hasRWConflict = this.rwConflicts[ts_procId][dtxn_procId];
boolean ts_hasWWConflict = this.wwConflicts[ts_procId][dtxn_procId];
if (debug.val)
LOG.debug(String.format("%s -> %s [R-W:%s / W-W:%s]",
candidate, dtxn, ts_hasRWConflict, ts_hasWWConflict));
// Sanity Check
assert(dtxn_hasWWConflict == ts_hasWWConflict);
// If there is no conflict whatsoever, then we want to let this mofo out of the bag right away
if ((dtxn_hasWWConflict || dtxn_hasRWConflict || ts_hasRWConflict || ts_hasWWConflict) == false) {
if (debug.val)
LOG.debug(String.format("No conflicts between %s<->%s",
dtxn, candidate));
return (false);
}
final ConflictSet dtxn_conflicts = dtxn_proc.getConflicts().get(ts_proc.getName());
final ConflictSet ts_conflicts = ts_proc.getConflicts().get(dtxn_proc.getName());
// If TS is going to write to something that DTXN will read or write, then
// we can let that slide as long as DTXN hasn't read from or written to those tables yet
if (dtxn_hasRWConflict || dtxn_hasWWConflict) {
assert(dtxn_conflicts != null) :
String.format("Unexpected null DTXN ConflictSet for %s -> %s",
dtxn_proc.getName(), ts_proc.getName());
// READ-WRITE
if (debug.val && dtxn_conflicts.getReadwriteconflicts().isEmpty() == false)
LOG.debug(String.format("Examining %d R-W Conflicts from %s -> %s",
dtxn_conflicts.getReadwriteconflicts().size(), dtxn_proc.getName(), ts_proc.getName()));
for (ConflictPair conflict : dtxn_conflicts.getReadwriteconflicts().values()) {
assert(conflict != null) :
String.format("Unexpected null DTXN R/W ConflictSet tables for %s [candidate=%s]",
dtxn_proc.getName(), ts_proc.getName());
for (TableRef ref : conflict.getTables().values()) {
assert(ref.getTable() != null) :
String.format("Unexpected null table reference %s [%s -> %s]",
ref.fullName(), dtxn_proc.getName(), ts_proc.getName());
if (dtxn.isTableReadOrWritten(partitionId, ref.getTable())) {
return (true);
}
} // FOR
} // FOR (R-W)
// WRITE-WRITE
if (debug.val && dtxn_conflicts.getWritewriteconflicts().isEmpty() == false)
LOG.debug(String.format("Examining %d W-W Conflicts from %s -> %s",
dtxn_conflicts.getWritewriteconflicts().size(), dtxn_proc.getName(), ts_proc.getName()));
for (ConflictPair conflict : dtxn_conflicts.getWritewriteconflicts().values()) {
assert(conflict != null) :
String.format("Unexpected null ConflictSet for %s [candidate=%s]",
dtxn_proc.getName(), ts_proc.getName());
for (TableRef ref : conflict.getTables().values()) {
assert(ref.getTable() != null) :
String.format("Unexpected null table reference %s [%s -> %s]",
ref.fullName(), dtxn_proc.getName(), ts_proc.getName());
if (dtxn.isTableReadOrWritten(partitionId, ref.getTable())) {
return (true);
}
}
} // FOR (W-W)
}
// Similarly, if the TS needs to read from (but not write to) a table that DTXN
// writes to, then we can allow TS to execute if DTXN hasn't written anything to
// those tables yet
if (ts_hasRWConflict && ts_hasWWConflict == false) {
assert(ts_conflicts != null) :
String.format("Unexpected null ConflictSet for %s -> %s",
ts_proc.getName(), dtxn_proc.getName());
if (debug.val) LOG.debug(String.format("%s has R-W conflict with %s. Checking read/write sets", candidate, dtxn));
for (ConflictPair conflict : ts_conflicts.getReadwriteconflicts().values()) {
assert(conflict != null) :
String.format("Unexpected null ConflictSet for %s [candidate=%s]",
dtxn_proc.getName(), ts_proc.getName());
for (TableRef ref : conflict.getTables().values()) {
assert(ref.getTable() != null) :
String.format("Unexpected null table reference %s [%s -> %s]",
ref.fullName(), dtxn_proc.getName(), ts_proc.getName());
if (dtxn.isTableWritten(partitionId, ref.getTable())) {
return (true);
}
} // FOR
} // FOR (R-W)
}
// If we get to this point, then we know that these two txns do not conflict
return (false);
}
@Override
public boolean hasConflictAfter(AbstractTransaction ts0, LocalTransaction ts1, int partitionId) {
return (false);
}
}