/// <summary> // Call from WC in response to a client's abort /// </summary> /// <param name="context"></param> public void Abort(TP.Transaction context) { // Get the list of names of the resource manager involved in this transaction List <string> rmNameList = GetRMListForTransaction(context); if (null == rmNameList) { System.Console.WriteLine(string.Format("Transaction {0} unknown", context.Id)); } // Get the list of resource managers involved in this transaction List <RM> rmList = new List <RM>(); lock (this.resourceManagers) { foreach (string name in rmNameList) { RM manager = GetResourceMananger(name); if (null == manager) { continue; } rmList.Add(manager); } // Write transaction id and list of RM to outstanding transaction list before aborting the transaction // Flush the entry to the outstanding transaction file immediately OutstandingTransactions.OutstandingTransactionsValue abortedTransactionValue = new OutstandingTransactions.OutstandingTransactionsValue( OutstandingTransactions.OutstandingTransactionsValue.TransactionType.Abort, rmNameList); lock (OutstandingTransactions) { OutstandingTransactions.UpdateAndFlush(context.Id.ToString(), abortedTransactionValue); System.Console.WriteLine(string.Format("Transaction {0} aborting...", context.Id)); for (int i = 0; i < rmList.Count; ++i) { ExecuteActionWithTimeout exec = new ExecuteActionWithTimeout(rmList[i].GetName(), () => rmList[i].Abort(context)); try { exec.Run(); } catch (TimeoutException) { System.Console.WriteLine(string.Format("Transaction {0} timed out while waiting for RM {1} to abort...", context.Id, rmList[i].GetName())); } } // PRESUMED ABORT: Once the TM sends out the abort, it forgets about the transaction immediately. // So we write transaction id and an _empty_ list of unacknowledged RMs to the outstanding transaction list. // This will cancel out the entry we logged earlier about the transaction and all its RMs. // Flush the entry to the outstanding transaction file immediately abortedTransactionValue.nackRMList.Clear(); OutstandingTransactions.UpdateAndFlush(context.Id.ToString(), abortedTransactionValue); if (abortedTransactionValue.nackRMList.Count == 0) { System.Console.WriteLine(string.Format("Transaction {0} aborted", context.Id)); } else { System.Console.WriteLine(string.Format("Transaction {0} aborted with some timeouts", context.Id)); } } } }
// This function reviews all outstanding transaction and reissues Commit() or Abort() to the // unackowledged RMs public void recovery() { Console.Out.WriteLine("Recovery started..."); List <string> deleteTransactionList = new List <string>(); // Keep track of transactions that are fully acknowledged by all its RMs lock (OutstandingTransactions) { if (OutstandingTransactions.transactionList.Keys.Count == 0) { Console.WriteLine("Recovery: No outstanding transactions to recover. Recovery completed."); return; } foreach (string transactionId in OutstandingTransactions.transactionList.Keys) { // For each outstanding transaction, get its transaction id and its OutstandingTransactions value OutstandingTransactions.OutstandingTransactionsValue entry = OutstandingTransactions.transactionList[transactionId]; Transaction context = new Transaction(); context.Id = new Guid(transactionId); Console.Out.WriteLine(string.Format("Recovery: Recovering transaction {0}...", transactionId)); // For each of the remaining NACK RMs, reissue the Commit or Abort for (int i = 0; i < entry.nackRMList.Count; ++i) { string rmName = entry.nackRMList[i]; RM rm = GetResourceMananger(rmName); if (rm == null) { Console.Out.WriteLine(string.Format("Recovery: Failed to find resource manager {0}", rmName)); } else { ExecuteActionWithTimeout exec; if (entry.transactionType == OutstandingTransactions.OutstandingTransactionsValue.TransactionType.Commit) { Console.Out.WriteLine(string.Format("Recovery: Re-committing resource manager {0}...", rmName)); exec = new ExecuteActionWithTimeout(rmName, () => rm.Commit(context)); } else { Console.Out.WriteLine(string.Format("Recovery: Re-aborting resource manager {0}...", rmName)); exec = new ExecuteActionWithTimeout(rmName, () => rm.Abort(context)); } // Execute the commit or abort try { exec.Run(); Console.Out.WriteLine("Recovery: Successful!"); // Remove RM from the current transaction since we received the Done message (ie the call didn't time out) OutstandingTransactions.transactionList[transactionId].nackRMList.RemoveAt(i); --i; } catch (TimeoutException) { System.Console.WriteLine("Recovery: Failed!"); } } } // PRESUMED ABORT: If the transaction is Abort, we don't care about the Done message and we forget about // the transaction immediately. // For commit transactions, mark transaction for removal if we received Done from all RMs if (entry.transactionType == OutstandingTransactions.OutstandingTransactionsValue.TransactionType.Abort || entry.nackRMList.Count == 0) { deleteTransactionList.Add(transactionId); } } // Delete transaction marked for removal from the outstanding transaction list foreach (string s in deleteTransactionList) { OutstandingTransactions.transactionList.Remove(s); } // Update file to reflect outstanding transactions status // This is a full rewrite of the outstanding transaction file and hence it will implicitly garbage collect // the transactions that are no longer outstanding. OutstandingTransactions.WriteToFile(); } Console.Out.WriteLine("Recovery completed."); }
/// <summary> // Call from WC in response to a client's commit. /// </summary> /// <param name="context"></param> public void Commit(TP.Transaction context) { // Get the list of names of the resource manager involved in this transaction List <string> rmNameList = GetRMListForTransaction(context); if (null == rmNameList) { System.Console.WriteLine(string.Format("Transaction {0} unknown", context.Id)); throw new AbortTransationException(); } // Get the list of resource managers involved in this transaction List <RM> rmList = new List <RM>(); lock (this.resourceManagers) { foreach (string name in rmNameList) { RM manager = GetResourceMananger(name); if (null == manager) { continue; } rmList.Add(manager); } // Request to prepare for all resource managers involved in this transaction bool allPrepared = true; List <ExecuteFuncWithTimeout <bool> > execPrepareList = new List <ExecuteFuncWithTimeout <bool> >(); try { // Execute the Prepare() function for each associated RMs with timeout for (int i = 0; i < rmList.Count; ++i) { ExecuteFuncWithTimeout <bool> exec = new ExecuteFuncWithTimeout <bool>(rmList[i].GetName(), () => rmList[i].Prepare(context)); execPrepareList.Add(exec); exec.Run(); } } // If a timeout occurs, it means that RMs are not all prepared and we should abort // the transaction catch (TimeoutException) { System.Console.WriteLine(string.Format("Transaction {0} timed out while waiting for RequestToPrepare. Aborting transaction...", context.Id)); allPrepared = false; } if (allPrepared) { // If all resource managers are ready responded to the request to prepare, check if // any of them responded NO to the request. foreach (ExecuteFuncWithTimeout <bool> exec in execPrepareList) { if (exec.completed && exec.result == false) { allPrepared = false; System.Console.WriteLine(string.Format("Transaction {0} received No for RequestToPrepare. Aborting transaction...", context.Id)); break; } } } execPrepareList.Clear(); // Initialize the list of RM to outstanding transaction list in case of recovery OutstandingTransactions.OutstandingTransactionsValue committedTransactionValue = new OutstandingTransactions.OutstandingTransactionsValue( OutstandingTransactions.OutstandingTransactionsValue.TransactionType.Commit, rmNameList); lock (OutstandingTransactions) { // If all resource managers responded Prepared to the Request to prepare, commit the transaction. if (allPrepared) { // Write transaction id and list of RM to outstanding transaction list before committing the transaction // Flush the entry to the outstanding transaction file immediately OutstandingTransactions.UpdateAndFlush(context.Id.ToString(), committedTransactionValue); System.Console.WriteLine(string.Format("Transaction {0} received Prepared from all resource managers. Committing transaction...", context.Id)); // Execute commit() in all associated RMs for (int i = 0; i < rmList.Count; ++i) { ExecuteActionWithTimeout exec = new ExecuteActionWithTimeout(rmList[i].GetName(), () => rmList[i].Commit(context)); try { exec.Run(); // Remove RM from list of RM when we received Done (ie no timeout has occurred) committedTransactionValue.nackRMList.Remove(rmList[i].GetName()); } catch (TimeoutException) { System.Console.WriteLine(string.Format("Transaction {0} timed out while waiting for RM {1} to commit...", context.Id, rmList[i].GetName())); } } // Write transaction id and list of unacknowledged RMs to the outstanding transaction list. // Flush the entry to the outstanding transaction file immediately OutstandingTransactions.UpdateAndFlush(context.Id.ToString(), committedTransactionValue); if (committedTransactionValue.nackRMList.Count == 0) { System.Console.WriteLine(string.Format("Transaction {0} commited", context.Id)); } else { System.Console.WriteLine(string.Format("Transaction {0} commited with some timeouts", context.Id)); } } else { // Initialize the list of RM to outstanding transaction list in case of recovery // Write transaction id and list of RM to outstanding transaction list before aborting the transaction // Flush the entry to the outstanding transaction file immediately committedTransactionValue.transactionType = OutstandingTransactions.OutstandingTransactionsValue.TransactionType.Abort; OutstandingTransactions.UpdateAndFlush(context.Id.ToString(), committedTransactionValue); System.Console.WriteLine(string.Format("Transaction {0} aborting...", context.Id)); // Execute abort() in all associated RMs for (int i = 0; i < rmList.Count; ++i) { ExecuteActionWithTimeout exec = new ExecuteActionWithTimeout(rmList[i].GetName(), () => rmList[i].Abort(context)); try { exec.Run(); } catch (TimeoutException) { System.Console.WriteLine(string.Format("Transaction {0} timed out while waiting for RM {1} to abort...", context.Id, rmList[i].GetName())); } } // PRESUMED ABORT: Once the TM sends out the abort, it forgets about the transaction immediately. // So we write transaction id and an _empty_ list of unacknowledged RMs to the outstanding transaction list. // This will cancel out the entry we logged earlier about the transaction and all its RMs. // Flush the entry to the outstanding transaction file immediately committedTransactionValue.nackRMList.Clear(); OutstandingTransactions.UpdateAndFlush(context.Id.ToString(), committedTransactionValue); if (committedTransactionValue.nackRMList.Count == 0) { System.Console.WriteLine(string.Format("Transaction {0} aborted", context.Id)); } else { System.Console.WriteLine(string.Format("Transaction {0} aborted with some timeouts", context.Id)); } } } } }