diff --git a/Projects/cs448p4/src/main/java/CC/CC.java b/Projects/cs448p4/src/main/java/CC/CC.java index 62ceddd..753e1c5 100644 --- a/Projects/cs448p4/src/main/java/CC/CC.java +++ b/Projects/cs448p4/src/main/java/CC/CC.java @@ -1,25 +1,418 @@ package CC; -import java.util.List; -public class 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. + * - 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) - { - //TODO - return null ; + 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; } }