private async Task StartTransactions(List <TimeSpan> startingTransactions, List <TaskCompletionSource <long> > startCompletions) { try { StartTransactionsResponse startResponse = await this.tmService.StartTransactions(startingTransactions); List <long> startedIds = startResponse.TransactionId; // reply to clients with results for (int i = 0; i < startCompletions.Count; i++) { TransactionsStatisticsGroup.OnTransactionStarted(); startCompletions[i].SetResult(startedIds[i]); } // Refresh cached values using new values from TM. this.ReadOnlyTransactionId = Math.Max(this.ReadOnlyTransactionId, startResponse.ReadOnlyTransactionId); this.abortLowerBound = Math.Max(this.abortLowerBound, startResponse.AbortLowerBound); logger.Verbose(ErrorCode.Transactions_ReceivedTMResponse, "{0} Transactions started. readOnlyTransactionId {1}, abortLowerBound {2}", startingTransactions.Count, ReadOnlyTransactionId, abortLowerBound); } catch (Exception e) { logger.Error(ErrorCode.Transactions_TMError, "Transaction manager failed to start transactions.", e); foreach (var completion in startCompletions) { TransactionsStatisticsGroup.OnTransactionStartFailed(); completion.SetException(new OrleansStartTransactionFailedException(e)); } } startingTransactions.Clear(); startCompletions.Clear(); }
private async Task ProcessRequests(object args) { // NOTE: This code is a bit complicated because we want to issue both start and commit requests, // but wait for each one separately in its own continuation. This can be significantly simplified // if we can register a separate timer for start and commit. List <TransactionInfo> committingTransactions = new List <TransactionInfo>(); List <TimeSpan> startingTransactions = new List <TimeSpan>(); List <TaskCompletionSource <long> > startCompletions = new List <TaskCompletionSource <long> >(); while (transactionCommitQueue.Count > 0 || transactionStartQueue.Count > 0 || outstandingCommits.Count > 0) { var initialAbortLowerBound = this.abortLowerBound; await Task.Yield(); await WaitForWork(); int startCount = transactionStartQueue.Count; while (startCount > 0 && startTransactionsTask.IsCompleted) { Tuple <TimeSpan, TaskCompletionSource <long> > elem; transactionStartQueue.TryDequeue(out elem); startingTransactions.Add(elem.Item1); startCompletions.Add(elem.Item2); startCount--; } int commitCount = transactionCommitQueue.Count; while (commitCount > 0 && commitTransactionsTask.IsCompleted) { TransactionInfo elem; transactionCommitQueue.TryDequeue(out elem); committingTransactions.Add(elem); outstandingCommits.Add(elem.TransactionId); commitCount--; } if (startingTransactions.Count > 0 && startTransactionsTask.IsCompleted) { logger.Verbose(ErrorCode.Transactions_SendingTMRequest, "Calling TM to start {0} transactions", startingTransactions.Count); startTransactionsTask = this.tmService.StartTransactions(startingTransactions).ContinueWith( async startRequest => { try { var startResponse = await startRequest; var startedIds = startResponse.TransactionId; // reply to clients with results for (int i = 0; i < startCompletions.Count; i++) { TransactionsStatisticsGroup.OnTransactionStarted(); startCompletions[i].SetResult(startedIds[i]); } // Refresh cached values using new values from TM. this.ReadOnlyTransactionId = Math.Max(this.ReadOnlyTransactionId, startResponse.ReadOnlyTransactionId); this.abortLowerBound = Math.Max(this.abortLowerBound, startResponse.AbortLowerBound); logger.Verbose(ErrorCode.Transactions_ReceivedTMResponse, "{0} transactions started. readOnlyTransactionId {1}, abortLowerBound {2}", startingTransactions.Count, ReadOnlyTransactionId, abortLowerBound); } catch (Exception e) { logger.Error(ErrorCode.Transactions_TMError, "", e); foreach (var completion in startCompletions) { TransactionsStatisticsGroup.OnTransactionStartFailed(); completion.SetException(new OrleansStartTransactionFailedException(e)); } } startingTransactions.Clear(); startCompletions.Clear(); }); } if ((committingTransactions.Count > 0 || outstandingCommits.Count > 0) && commitTransactionsTask.IsCompleted) { logger.Verbose(ErrorCode.Transactions_SendingTMRequest, "Calling TM to commit {0} transactions", committingTransactions.Count); commitTransactionsTask = this.tmService.CommitTransactions(committingTransactions, outstandingCommits).ContinueWith( async commitRequest => { try { var commitResponse = await commitRequest; var commitResults = commitResponse.CommitResult; // reply to clients with the outcomes we received from the TM. foreach (var completedId in commitResults.Keys) { outstandingCommits.Remove(completedId); TaskCompletionSource <bool> completion; if (commitCompletions.TryRemove(completedId, out completion)) { if (commitResults[completedId].Success) { TransactionsStatisticsGroup.OnTransactionCommitted(); completion.SetResult(true); } else { if (commitResults[completedId].AbortingException != null) { TransactionsStatisticsGroup.OnTransactionAborted(); completion.SetException(commitResults[completedId].AbortingException); } else { TransactionsStatisticsGroup.OnTransactionInDoubt(); completion.SetException(new OrleansTransactionInDoubtException(completedId)); } } } } // Refresh cached values using new values from TM. this.ReadOnlyTransactionId = Math.Max(this.ReadOnlyTransactionId, commitResponse.ReadOnlyTransactionId); this.abortLowerBound = Math.Max(this.abortLowerBound, commitResponse.AbortLowerBound); logger.Verbose(ErrorCode.Transactions_ReceivedTMResponse, "{0} transactions committed. readOnlyTransactionId {1}, abortLowerBound {2}", committingTransactions.Count, ReadOnlyTransactionId, abortLowerBound); } catch (Exception e) { logger.Error(ErrorCode.Transactions_TMError, "", e); // Propagate the exception to every transaction in the request. foreach (var tx in committingTransactions) { TransactionsStatisticsGroup.OnTransactionInDoubt(); TaskCompletionSource <bool> completion; if (commitCompletions.TryRemove(tx.TransactionId, out completion)) { outstandingCommits.Remove(tx.TransactionId); completion.SetException(new OrleansTransactionInDoubtException(tx.TransactionId)); } } } committingTransactions.Clear(); }); // Removed transactions below the abort lower bound. if (this.abortLowerBound != initialAbortLowerBound) { foreach (var aborted in this.abortedTransactions) { if (aborted.Key < this.abortLowerBound) { long ignored; this.abortedTransactions.TryRemove(aborted.Key, out ignored); } } } } } }