package edu.brown.hstore.specexec.checkers; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import org.apache.commons.collections15.CollectionUtils; import org.apache.log4j.Logger; import org.voltdb.CatalogContext; import org.voltdb.VoltTable; import org.voltdb.catalog.Table; import org.voltdb.jni.ExecutionEngine; import org.voltdb.utils.VoltTableUtil; import edu.brown.hstore.txns.AbstractTransaction; import edu.brown.hstore.txns.LocalTransaction; import edu.brown.logging.LoggerUtil; import edu.brown.logging.LoggerUtil.LoggerBoolean; import edu.brown.utils.StringUtil; /** * OCC Conflict Checker * This relies on the EE to generate the txn's read/write tracking sets * @author pavlo */ public class OptimisticConflictChecker extends AbstractConflictChecker { private static final Logger LOG = Logger.getLogger(OptimisticConflictChecker.class); private static final LoggerBoolean debug = new LoggerBoolean(); private static final LoggerBoolean trace = new LoggerBoolean(); static { LoggerUtil.attachObserver(LOG, debug, trace); } protected static final int READ = 0; protected static final int WRITE = 1; private final ExecutionEngine ee; public OptimisticConflictChecker(CatalogContext catalogContext, ExecutionEngine ee) { super(catalogContext); this.ee = ee; } @Override public boolean shouldIgnoreTransaction(AbstractTransaction ts) { if (ts instanceof LocalTransaction) { return (((LocalTransaction)ts).getRestartCounter() > 0); } return (false); } @Override public boolean hasConflictBefore(AbstractTransaction ts0, LocalTransaction ts1, int partitionId) { return (false); } @Override public boolean hasConflictAfter(AbstractTransaction ts0, LocalTransaction ts1, int partitionId) { assert(ts0.isInitialized()) : String.format("Uninitialized distributed transaction handle [%s]", ts0); assert(ts1.isInitialized()) : String.format("Uninitialized speculative transaction handle [%s]", ts1); // Get the READ/WRITE tracking sets from the EE VoltTable tsTracking0[] = this.getReadWriteSets(ts0); if (trace.val) LOG.trace(String.format("%s READ/WRITE SETS:\n%s", ts0, VoltTableUtil.format(tsTracking0))); VoltTable tsTracking1[] = this.getReadWriteSets(ts1); if (trace.val) LOG.trace(String.format("%s READ/WRITE SETS:\n%s", ts1, VoltTableUtil.format(tsTracking1))); // SPECIAL CASE // Either txn did not actually read or write anything at this partition, so we can just // say that everything kosher. if ((tsTracking0[READ] == null || tsTracking0[READ].getRowCount() == 0) && (tsTracking0[WRITE] == null || tsTracking0[WRITE].getRowCount() == 0)) { // SANITY CHECK assert((tsTracking0[READ] == null || tsTracking0[READ].getRowCount() == 0) && (ts0.getTableIdsMarkedRead(partitionId).length == 0)) : String.format("%s READ Set: %s\nTables Read: %s", ts0, tsTracking0[READ], Arrays.toString(ts0.getTableIdsMarkedRead(partitionId))); assert((tsTracking0[WRITE] == null || tsTracking0[WRITE].getRowCount() == 0) && (ts0.getTableIdsMarkedWritten(partitionId).length == 0)) : String.format("%s WRITE Set: %s\nTables Written: %s", ts0, tsTracking0[WRITE], Arrays.toString(ts0.getTableIdsMarkedWritten(partitionId))); return (false); } else if ((tsTracking1[READ] == null || tsTracking1[READ].getRowCount() == 0) && (tsTracking1[WRITE] == null || tsTracking1[WRITE].getRowCount() == 0)) { // SANITY CHECK assert((tsTracking1[READ] == null || tsTracking1[READ].getRowCount() == 0) && (ts1.getTableIdsMarkedRead(partitionId).length == 0)) : String.format("%s READ Set: %s\nTables Read: %s", ts1, tsTracking1[READ], Arrays.toString(ts1.getTableIdsMarkedRead(partitionId))); assert((tsTracking1[WRITE] == null || tsTracking1[WRITE].getRowCount() == 0) && (ts1.getTableIdsMarkedWritten(partitionId).length == 0)) : String.format("%s WRITE Set: %s\nTables Written: %s", ts1, tsTracking1[WRITE], Arrays.toString(ts1.getTableIdsMarkedWritten(partitionId))); return (false); } int tableIds[] = null; // READ-WRITE CONFLICTS tableIds = ts0.getTableIdsMarkedRead(partitionId); if (this.hasTupleConflict(partitionId, tableIds, ts0, tsTracking0[READ], ts1, tsTracking1[WRITE])) { if (debug.val) LOG.debug(String.format("Found READ-WRITE conflict between %s and %s", ts0, ts1)); return (true); } // WRITE-WRITE CONFLICTS tableIds = ts0.getTableIdsMarkedWritten(partitionId); if (this.hasTupleConflict(partitionId, tableIds, ts0, tsTracking0[WRITE], ts1, tsTracking1[WRITE])) { if (debug.val) LOG.debug(String.format("Found WRITE-WRITE conflict between %s and %s", ts0, ts1)); return (true); } return (false); } /** * Returns true if there is a conflict between the two transactions for the * given list of tableIds based on their tracking sets. * @param partition * @param tableIds * @param ts0 * @param tsTracking0 * @param ts1 * @param tsTracking1 * @return */ protected boolean hasTupleConflict(int partition, int tableIds[], AbstractTransaction ts0, VoltTable tsTracking0, AbstractTransaction ts1, VoltTable tsTracking1) { // Check whether the first transaction accessed the same tuple by the second txn Set<Integer> tupleIds0 = new HashSet<Integer>(); Set<Integer> tupleIds1 = new HashSet<Integer>(); if (trace.val) LOG.trace("\n"+ StringUtil.columns(ts0+"\n"+VoltTableUtil.format(tsTracking0), ts1+"\n"+VoltTableUtil.format(tsTracking1))); for (int tableId : tableIds) { Table targetTbl = catalogContext.getTableById(tableId); // Skip if we know that the other transaction hasn't done anything // to the table at this partition. if (ts0.isTableReadOrWritten(partition, targetTbl) == false) { continue; } tupleIds0.clear(); this.getTupleIds(targetTbl.getName(), tsTracking0, tupleIds0); tupleIds1.clear(); this.getTupleIds(targetTbl.getName(), tsTracking1, tupleIds1); if (debug.val) { Map<String, Object> m = new LinkedHashMap<String, Object>(); m.put(targetTbl.getName() + " TRACKING SETS", null); m.put(ts0.toString(), tupleIds0); m.put(ts1.toString(), tupleIds1); LOG.debug(StringUtil.formatMaps(m)); } if (CollectionUtils.containsAny(tupleIds0, tupleIds1)) { if (debug.val) LOG.debug(String.format("Found conflict in %s between %s and %s", targetTbl, ts0, ts1)); return (true); } } // FOR return (false); } protected void getTupleIds(String targetTableName, VoltTable tsTracking, Collection<Integer> tupleIds) { tsTracking.resetRowPosition(); while (tsTracking.advanceRow()) { String tableName = tsTracking.getString(0); if (targetTableName.equalsIgnoreCase(tableName)) { tupleIds.add((int)tsTracking.getLong(1)); } } // WHILE } protected VoltTable[] getReadWriteSets(AbstractTransaction ts) { VoltTable readSet, writeSet; try { readSet = this.ee.trackingReadSet(ts.getTransactionId()); writeSet = this.ee.trackingWriteSet(ts.getTransactionId()); } catch (Exception ex) { String msg = String.format("Failed to get read/write tracking set for %s", ts); throw new RuntimeException(msg, ex); } return (new VoltTable[]{ readSet, writeSet }); } }