public async Task Commit(ITransactionInfo info) { var transactionInfo = (TransactionInfo)info; TransactionsStatisticsGroup.OnTransactionCommitRequest(); if (transactionInfo.IsReadOnly) { return; } var completion = new TaskCompletionSource <bool>(); bool canCommit = true; List <Task <bool> > prepareTasks = new List <Task <bool> >(transactionInfo.WriteSet.Count); foreach (var g in transactionInfo.WriteSet.Keys) { TransactionalResourceVersion write = TransactionalResourceVersion.Create(transactionInfo.TransactionId, transactionInfo.WriteSet[g]); TransactionalResourceVersion?read = null; if (transactionInfo.ReadSet.ContainsKey(g)) { read = transactionInfo.ReadSet[g]; transactionInfo.ReadSet.Remove(g); } prepareTasks.Add(g.Prepare(transactionInfo.TransactionId, write, read)); } foreach (var g in transactionInfo.ReadSet.Keys) { TransactionalResourceVersion read = transactionInfo.ReadSet[g]; prepareTasks.Add(g.Prepare(transactionInfo.TransactionId, null, read)); } await Task.WhenAll(prepareTasks); foreach (var t in prepareTasks) { if (!t.Result) { canCommit = false; } } if (!canCommit) { TransactionsStatisticsGroup.OnTransactionAborted(); abortedTransactions.TryAdd(transactionInfo.TransactionId, 0); throw new OrleansPrepareFailedException(transactionInfo.TransactionId); } commitCompletions.TryAdd(transactionInfo.TransactionId, completion); transactionCommitQueue.Enqueue(transactionInfo); await completion.Task; }
private async Task CommitTransactions(List <TransactionInfo> committingTransactions, HashSet <long> outstandingCommits) { var stopWatch = Stopwatch.StartNew(); try { metrics.BatchCommitTransactionsRequestsCounter++; metrics.BatchCommitTransactionsRequestSizeCounter += committingTransactions.Count; CommitTransactionsResponse commitResponse; try { commitResponse = await this.tmService.CommitTransactions(committingTransactions, outstandingCommits); } finally { stopWatch.Stop(); metrics.BatchCommitTransactionsRequestLatencyCounter += stopWatch.Elapsed; } 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.Debug(ErrorCode.Transactions_ReceivedTMResponse, "{0} transactions committed. readOnlyTransactionId {1}, abortLowerBound {2}", committingTransactions.Count, ReadOnlyTransactionId, abortLowerBound); } catch (Exception e) { logger.Error(ErrorCode.Transactions_TMError, "TM Error", 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(); }
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.StartTransactions(startingTransactions, startCompletions); } 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); } } } } } }