package CC; import java.util.List; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.Queue; enum LockState { NONE, SHARED, EXCLUSIVE } class Lock { private ArrayList transactionIds; private LockState type; private int noReads; public Lock() { this.transactionIds = new ArrayList(); this.noReads = 0; this.type = LockState.NONE; } public ArrayList getTransactionIds() { return transactionIds; } public void addTransactionId(int transactionId) { if (!transactionIds.contains(Integer.valueOf(transactionId))) { transactionIds.add(transactionId); noReads++; } } public LockState getType() { return type; } public void setType(LockState type) { this.type = type; } public int getNoReads() { return noReads; } public boolean hasLock(int transactionId) { return transactionIds.contains(Integer.valueOf(transactionId)); } public void release(int releaseTransactionId) { transactionIds.remove(Integer.valueOf(releaseTransactionId)); noReads--; if (noReads == 0) { this.type = LockState.NONE; } } } class LockTable { private Lock[] locks; private static final int NUM_RECORDS = 10; public LockTable() { this.locks = new Lock[NUM_RECORDS]; // initialize the locktable for (int recordId = 0; recordId < NUM_RECORDS; recordId++) { locks[recordId] = new Lock(); } } public Lock getLock(int recordId) { if (recordId >= 0 && recordId < locks.length) { return locks[recordId]; } else { throw new IllegalArgumentException("Invalid recordId: " + recordId); } } public void releaseLocksForTransaction(int[] recordIds, int transactionId) { for (int recordId : recordIds) { if (recordId >= 0 && recordId < locks.length) { locks[recordId].release(transactionId); } else { throw new IllegalArgumentException("Invalid recordId: " + recordId); } } } } interface Operation { int getRecordId(); int getValue(); } class WriteOp implements Operation { private int recordId; private int value; public WriteOp(int recordId, int value) { this.recordId = recordId; this.value = value; } public int getRecordId() { return recordId; } public int getValue() { return value; } } class ReadOp implements Operation { private int recordId; public ReadOp(int recordId) { this.recordId = recordId; } public int getRecordId() { return recordId; } public int getValue() { return -1; } } class CommitOp implements Operation { public int getRecordId() { return -1; } public int getValue() { return -1; } } class Transaction { private int currentOperationIndex; private Operation[] operations; private int transactionId; private ArrayList recordLocks; private int prevTimestamp; private boolean aborted; public Transaction(String[] operations, int transactionId) { this.currentOperationIndex = 0; this.operations = new Operation[operations.length]; this.transactionId = transactionId; this.recordLocks = new ArrayList(); this.prevTimestamp = -1; this.aborted = false; int OpIndex = 0; for (String operation : operations) { if (operation.charAt(0) == 'W') { this.operations[OpIndex] = new WriteOp(Character.getNumericValue(operation.charAt(2)), Character.getNumericValue(operation.charAt(4))); } else if (operation.charAt(0) == 'R') { this.operations[OpIndex] = new ReadOp(Character.getNumericValue(operation.charAt(2))); } else { this.operations[OpIndex] = new CommitOp(); } OpIndex++; } } public int getCurrentOperationIndex() { return currentOperationIndex; } public int getTransactionId() { return transactionId; } public void setCurrentOperationIndex(int currentOperationIndex) { this.currentOperationIndex = currentOperationIndex; } public Operation[] getOperations() { return operations; } public boolean hasNextOperation() { return (currentOperationIndex < operations.length) && !aborted; } public int doNextOperation(LockTable lockTable, int[] database, ArrayList systemLog, int timestamp, LinkedList[] adj) { if (hasNextOperation() && !aborted) { Operation nextOp = operations[currentOperationIndex]; Lock nextLock = null; if (!(nextOp instanceof CommitOp)) { nextLock = lockTable.getLock(nextOp.getRecordId()); } currentOperationIndex++; if (nextOp instanceof ReadOp) { if (!(nextLock.hasLock(transactionId))) { // The current transaction doesn't already have the lock. if (nextLock.getType() == LockState.EXCLUSIVE) { currentOperationIndex--; for (int adjTransactionId : nextLock.getTransactionIds()) { if (adjTransactionId != transactionId && !adj[transactionId].contains(Integer.valueOf(adjTransactionId))) { adj[transactionId].add(adjTransactionId); } } return timestamp; // The lock on the record is exclusive. } else { nextLock.setType(LockState.SHARED); nextLock.addTransactionId(transactionId); recordLocks.add(nextOp.getRecordId()); } } String[] log = { "R", Integer.toString(timestamp), "T" + Integer.toString(transactionId), Integer.toString(nextOp.getRecordId()), Integer.toString(database[nextOp.getRecordId()]), Integer.toString(prevTimestamp) }; systemLog.add(log); } else if (nextOp instanceof WriteOp) { int prevValue = database[nextOp.getRecordId()]; if ((nextLock.hasLock(transactionId) && nextLock.getNoReads() == 1) || nextLock.getType() == LockState.NONE) { nextLock.setType(LockState.EXCLUSIVE); database[nextOp.getRecordId()] = nextOp.getValue(); nextLock.addTransactionId(transactionId); if (!recordLocks.contains(Integer.valueOf(nextOp.getRecordId()))) { recordLocks.add(nextOp.getRecordId()); } } else { currentOperationIndex--; for (int adjTransactionId : nextLock.getTransactionIds()) { if (adjTransactionId != transactionId && !adj[transactionId].contains(Integer.valueOf(adjTransactionId))) { adj[transactionId].add(adjTransactionId); } } return timestamp; } String[] log = { "W", Integer.toString(timestamp), "T" + Integer.toString(transactionId), Integer.toString(nextOp.getRecordId()), Integer.toString(prevValue), Integer.toString(database[nextOp.getRecordId()]), Integer.toString(prevTimestamp) }; systemLog.add(log); } else { for (int recordLock : recordLocks) { lockTable.getLock(recordLock).release(transactionId); } recordLocks = new ArrayList(); String[] log = { "C", Integer.toString(timestamp), "T" + Integer.toString(transactionId), Integer.toString(prevTimestamp) }; systemLog.add(log); } prevTimestamp = timestamp; timestamp++; } return timestamp; } public void reset() { currentOperationIndex = 0; recordLocks = new ArrayList(); } public int abortTransaction(LockTable lockTable, int[] database, ArrayList systemLog, int timestamp, LinkedList[] adj) { aborted = true; int pastTransaction = prevTimestamp; do { String[] lastLog = systemLog.get(pastTransaction); if (lastLog[0] == "W") { database[Integer.parseInt(lastLog[3])] = Integer.parseInt(lastLog[4]); } pastTransaction = Integer.parseInt(lastLog[lastLog.length - 1]); } while (pastTransaction != -1); for (int recordLock : recordLocks) { lockTable.getLock(recordLock).release(transactionId); } recordLocks = new ArrayList(); adj[transactionId] = new LinkedList(); String[] log = { "A", Integer.toString(timestamp), "T" + Integer.toString(transactionId), Integer.toString(prevTimestamp) }; systemLog.add(log); return -1; } } public class CC { private static final int NUM_RECORDS = 10; /** * Notes: * - Execute all given transactions, using locking. * - Each element in the transactions List represents all operations performed * by one transaction, in order. * - No operation in a transaction can be executed out of order, but operations * may be interleaved with other * transactions if allowed by the locking. * - The index of the transaction in the list is equivalent to the transaction * ID. * - Print the log to the console at the end of the method. * - Return the new db state after executing the transactions. * * @param db the initial status of the db * @param transactions the schedule, which basically is a {@link List} of * transactions. * @return the final status of the db */ public static int[] executeSchedule(int[] db, List transactions) { LockTable lockTable = new LockTable(); Queue transactionQueue = new LinkedList<>(); Transaction[] transactionsArray = new Transaction[transactions.size() + 1]; int newTransactionId = 1; int[] database = new int[NUM_RECORDS]; ArrayList systemLog = new ArrayList<>(); int timestamp = 0; LinkedList[] adj; for (int i = 0; i < NUM_RECORDS; i++) { database[i] = i; } for (String transaction : transactions) { Transaction newTransaction = new Transaction(transaction.split(";"), newTransactionId); transactionQueue.add(newTransaction); transactionsArray[newTransactionId] = newTransaction; newTransactionId++; } adj = new LinkedList[transactions.size() + 1]; for (int i = 0; i < transactions.size() + 1; ++i) { adj[i] = new LinkedList(); } while (!transactionQueue.isEmpty()) { Transaction curTransaction = transactionQueue.remove(); if (curTransaction.hasNextOperation()) { timestamp = curTransaction.doNextOperation(lockTable, database, systemLog, timestamp, adj); // Here is where we check if there are deadlocks. We run DFS. boolean[] visited = new boolean[transactions.size() + 1]; int[] predecessor = new int[transactions.size() + 1]; ArrayList nodesInCycle = DFS(curTransaction.getTransactionId(), visited, adj, predecessor); if (nodesInCycle != null) { Collections.sort(nodesInCycle); transactionsArray[nodesInCycle.get(nodesInCycle.size() - 1)].abortTransaction(lockTable, database, systemLog, timestamp, adj); timestamp++; } transactionQueue.add(curTransaction); } } for (String[] log : systemLog) { StringBuilder sb = new StringBuilder(log[0] + ":" + log[1]); for (int k = 2; k < log.length; k++) { sb.append("," + log[k]); } System.out.println(sb.toString()); } System.out.println(); return database; } public static ArrayList DFS(int v, boolean visited[], LinkedList adj[], int predecessor[]) { visited[v] = true; Iterator i = adj[v].listIterator(); while (i.hasNext()) { int n = i.next(); if (!visited[n]) { predecessor[n] = v; return DFS(n, visited, adj, predecessor); } else { return returnNodesInCycle(predecessor, v, n); } } return null; } public static ArrayList returnNodesInCycle(int[] predecessor, int parent, int start) { ArrayList nodesInCycle = new ArrayList<>(); nodesInCycle.add(parent); nodesInCycle.add(start); while (predecessor[start] != parent && predecessor[start] != 0) { nodesInCycle.add(predecessor[start]); start = predecessor[start]; } return nodesInCycle; } }