|
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
namespace System.Activities.DurableInstancing
{
using System.Data;
using System.Data.SqlClient;
using System.Globalization;
using System.Linq;
using System.Runtime;
using System.Transactions;
using System.Runtime.Diagnostics;
sealed class SqlCommandAsyncResult : TransactedAsyncResult
{
static readonly TimeSpan MaximumOpenTimeout = TimeSpan.FromMinutes(2);
static readonly RetryErrorCode[] retryErrorCodes =
{
new RetryErrorCode(-2, RetryErrorOptions.RetryBeginOrEnd | RetryErrorOptions.RetryWhenTransaction), // SqlError: Timeout
new RetryErrorCode(20, RetryErrorOptions.RetryBeginOrEnd | RetryErrorOptions.RetryWhenTransaction), // SQL Azure error - a connection failed early in the login process. to SQL Server.
new RetryErrorCode(53, RetryErrorOptions.RetryBeginOrEnd | RetryErrorOptions.RetryWhenTransaction), //A network-related or instance-specific error occurred while establishing a connection to SQL Server.
new RetryErrorCode(64, RetryErrorOptions.RetryBeginOrEnd | RetryErrorOptions.RetryWhenTransaction), //A transport-level error has occurred when receiving results from the server (TCP Provider, error: 0 - The specified network name is no longer available).
new RetryErrorCode(121, RetryErrorOptions.RetryBeginOrEnd), // A transport-level error has occurred
new RetryErrorCode(233, RetryErrorOptions.RetryBeginOrEnd | RetryErrorOptions.RetryWhenTransaction), // Severed shared memory/named pipe connection drawn from the pool
new RetryErrorCode(1205, RetryErrorOptions.RetryBeginOrEnd), // Deadlock
new RetryErrorCode(1222, RetryErrorOptions.RetryBeginOrEnd | RetryErrorOptions.RetryWhenTransaction), // Lock Request Timeout
new RetryErrorCode(3910, RetryErrorOptions.RetryOnBegin | RetryErrorOptions.RetryWhenTransaction), // Transaction context in use by another session.
new RetryErrorCode(4060, RetryErrorOptions.RetryBeginOrEnd | RetryErrorOptions.RetryWhenTransaction), // Database not online
new RetryErrorCode(8645, RetryErrorOptions.RetryBeginOrEnd | RetryErrorOptions.RetryWhenTransaction), // A timeout occurred while waiting for memory resources
new RetryErrorCode(8641, RetryErrorOptions.RetryBeginOrEnd | RetryErrorOptions.RetryWhenTransaction), // Could not perform the operation because the requested memory grant was not available
new RetryErrorCode(10053, RetryErrorOptions.RetryBeginOrEnd | RetryErrorOptions.RetryWhenTransaction), // A transport-level error has occurred when receiving results from the server
new RetryErrorCode(10054, RetryErrorOptions.RetryBeginOrEnd | RetryErrorOptions.RetryWhenTransaction), // Severed tcp connection drawn from the pool
new RetryErrorCode(10060, RetryErrorOptions.RetryBeginOrEnd | RetryErrorOptions.RetryWhenTransaction), // The server was not found or was not accessible.
new RetryErrorCode(10061, RetryErrorOptions.RetryBeginOrEnd | RetryErrorOptions.RetryWhenTransaction), // SQL Server not started
new RetryErrorCode(10928, RetryErrorOptions.RetryBeginOrEnd | RetryErrorOptions.RetryWhenTransaction), // SQL Azure error - The limit for the database resource has been reached.
new RetryErrorCode(10929, RetryErrorOptions.RetryBeginOrEnd | RetryErrorOptions.RetryWhenTransaction), // SQL Azure error - The server is currently too busy to support requests up to the maximum limit.
new RetryErrorCode(40143, RetryErrorOptions.RetryBeginOrEnd | RetryErrorOptions.RetryWhenTransaction), // SQL Azure error - server encountered error processing the request.
new RetryErrorCode(40197, RetryErrorOptions.RetryBeginOrEnd | RetryErrorOptions.RetryWhenTransaction), // SQL Azure error - server encountered error processing the request.
new RetryErrorCode(40501, RetryErrorOptions.RetryBeginOrEnd | RetryErrorOptions.RetryWhenTransaction), // SQL Azure error - server is currently busy.
new RetryErrorCode(40549, RetryErrorOptions.RetryBeginOrEnd | RetryErrorOptions.RetryWhenTransaction), // SQL Azure error - transaction blocking system calls
new RetryErrorCode(40553, RetryErrorOptions.RetryBeginOrEnd | RetryErrorOptions.RetryWhenTransaction), // SQL Azure error - excessive memory usage
new RetryErrorCode(40613, RetryErrorOptions.RetryBeginOrEnd | RetryErrorOptions.RetryWhenTransaction) // SQL Azure error - database on server is not available.
};
static AsyncCompletion onExecuteReaderCallback = new AsyncCompletion(OnExecuteReader);
static AsyncCompletion onRetryCommandCallback = new AsyncCompletion(OnRetryCommand);
string connectionString;
DependentTransaction dependentTransaction;
int maximumRetries;
int retryCount;
EventTraceActivity eventTraceActivity;
SqlCommand sqlCommand;
SqlDataReader sqlDataReader;
TimeoutHelper timeoutHelper;
public SqlCommandAsyncResult(SqlCommand sqlCommand, string connectionString, EventTraceActivity eventTraceActivity, DependentTransaction dependentTransaction,
TimeSpan timeout, int retryCount, int maximumRetries, AsyncCallback callback, object state)
: base(callback, state)
{
long openTimeout = Math.Min(timeout.Ticks, SqlCommandAsyncResult.MaximumOpenTimeout.Ticks);
this.sqlCommand = sqlCommand;
this.connectionString = connectionString;
this.eventTraceActivity = eventTraceActivity;
this.dependentTransaction = dependentTransaction;
this.timeoutHelper = new TimeoutHelper(TimeSpan.FromTicks(openTimeout));
this.retryCount = retryCount;
this.maximumRetries = maximumRetries;
}
[Flags]
enum RetryErrorOptions
{
RetryOnBegin = 1,
RetryOnEnd = 2,
RetryWhenTransaction = 4,
RetryBeginOrEnd = RetryOnBegin | RetryOnEnd
}
public static SqlDataReader End(IAsyncResult result)
{
SqlCommandAsyncResult SqlCommandAsyncResult = AsyncResult.End<SqlCommandAsyncResult>(result);
return SqlCommandAsyncResult.sqlDataReader;
}
public void StartCommand()
{
StartCommandInternal(true);
}
static bool OnExecuteReader(IAsyncResult result)
{
SqlCommandAsyncResult thisPtr = (SqlCommandAsyncResult)(result.AsyncState);
return thisPtr.CompleteExecuteReader(result);
}
static bool OnRetryCommand(IAsyncResult childPtr)
{
SqlCommandAsyncResult parentPtr = (SqlCommandAsyncResult)(childPtr.AsyncState);
parentPtr.sqlDataReader = SqlCommandAsyncResult.End(childPtr);
return true;
}
static bool ShouldRetryForSqlError(int error, RetryErrorOptions retryErrorOptions)
{
if (Transaction.Current != null)
{
retryErrorOptions |= RetryErrorOptions.RetryWhenTransaction;
}
return SqlCommandAsyncResult.retryErrorCodes.Any(x => x.ErrorCode == error && (x.RetryErrorOptions & retryErrorOptions) == retryErrorOptions);
}
static void StartCommandCallback(object state)
{
SqlCommandAsyncResult thisPtr = (SqlCommandAsyncResult) state;
try
{
// this can throw on the sync path - we need to signal the callback
thisPtr.StartCommandInternal(false);
}
catch (Exception e)
{
if (Fx.IsFatal(e))
{
throw;
}
if (thisPtr.sqlCommand.Connection != null)
{
thisPtr.sqlCommand.Connection.Close();
}
thisPtr.Complete(false, e);
}
}
bool CheckRetryCount()
{
return (++this.retryCount < maximumRetries);
}
bool CheckRetryCountAndTimer()
{
return (this.CheckRetryCount() && !this.HasOperationTimedOut());
}
bool CompleteExecuteReader(IAsyncResult result)
{
bool completeSelf = true;
try
{
this.sqlDataReader = this.sqlCommand.EndExecuteReader(result);
}
catch (SqlException exception)
{
if (TD.SqlExceptionCaughtIsEnabled())
{
TD.SqlExceptionCaught(this.eventTraceActivity, exception.Number.ToString(CultureInfo.InvariantCulture), exception.Message);
}
if (this.sqlDataReader != null)
{
this.sqlDataReader.Close();
}
if (this.sqlCommand.Connection != null)
{
this.sqlCommand.Connection.Close();
}
// If we completed sync then any retry is done by the original caller.
if (!result.CompletedSynchronously)
{
if (this.CheckRetryCountAndTimer() && ShouldRetryForSqlError(exception.Number, RetryErrorOptions.RetryOnEnd))
{
if (this.EnqueueRetry())
{
if (TD.RetryingSqlCommandDueToSqlErrorIsEnabled())
{
TD.RetryingSqlCommandDueToSqlError(this.eventTraceActivity, exception.Number.ToString(CultureInfo.InvariantCulture));
}
completeSelf = false;
}
}
}
if (completeSelf)
{
if (this.retryCount == maximumRetries && TD.MaximumRetriesExceededForSqlCommandIsEnabled())
{
TD.MaximumRetriesExceededForSqlCommand(this.eventTraceActivity);
}
throw;
}
}
return completeSelf;
}
bool EnqueueRetry()
{
bool result = false;
int delay = this.GetRetryDelay();
if (this.timeoutHelper.RemainingTime().TotalMilliseconds > delay)
{
this.sqlCommand.Dispose();
IOThreadTimer iott = new IOThreadTimer(StartCommandCallback, new SqlCommandAsyncResult(CloneSqlCommand(this.sqlCommand), this.connectionString, this.eventTraceActivity, this.dependentTransaction,
this.timeoutHelper.RemainingTime(), this.retryCount, this.maximumRetries, this.PrepareAsyncCompletion(onRetryCommandCallback), this), false);
iott.Set(delay);
if (TD.QueuingSqlRetryIsEnabled())
{
TD.QueuingSqlRetry(this.eventTraceActivity, delay.ToString(CultureInfo.InvariantCulture));
}
result = true;
}
return result;
}
static SqlCommand CloneSqlCommand(SqlCommand command)
{
//We do not want to use SqlCommand.Clone here because we do not want to replicate the parameters
SqlCommand newCommand = new SqlCommand()
{
CommandType = command.CommandType,
CommandText = command.CommandText,
};
SqlParameter[] tempParameterList = new SqlParameter[command.Parameters.Count];
for (int i = 0; i < command.Parameters.Count; i++)
{
tempParameterList[i] = command.Parameters[i];
}
command.Parameters.Clear();
newCommand.Parameters.AddRange(tempParameterList);
return newCommand;
}
int GetRetryDelay()
{
return 1000;
}
bool HasOperationTimedOut()
{
return (this.timeoutHelper.RemainingTime() <= TimeSpan.Zero);
}
void StartCommandInternal(bool synchronous)
{
if (!this.HasOperationTimedOut())
{
try
{
IAsyncResult result;
using (this.PrepareTransactionalCall(this.dependentTransaction))
{
AsyncCallback wrappedCallback = this.PrepareAsyncCompletion(onExecuteReaderCallback);
this.sqlCommand.Connection = StoreUtilities.CreateConnection(this.connectionString);
if (!this.HasOperationTimedOut())
{
result = this.sqlCommand.BeginExecuteReader(wrappedCallback, this, CommandBehavior.CloseConnection);
}
else
{
this.sqlCommand.Connection.Close();
this.Complete(synchronous, new TimeoutException(SR.TimeoutOnSqlOperation(this.timeoutHelper.OriginalTimeout.ToString())));
return;
}
}
if (this.CheckSyncContinue(result))
{
if (this.CompleteExecuteReader(result))
{
this.Complete(synchronous);
}
}
return;
}
catch (SqlException exception)
{
if (TD.SqlExceptionCaughtIsEnabled())
{
TD.SqlExceptionCaught(this.eventTraceActivity, exception.Number.ToString(null, CultureInfo.InvariantCulture), exception.Message);
}
if (this.sqlCommand.Connection != null)
{
this.sqlCommand.Connection.Close();
}
if (!this.CheckRetryCount() || !ShouldRetryForSqlError(exception.Number, RetryErrorOptions.RetryOnBegin))
{
throw;
}
if (TD.RetryingSqlCommandDueToSqlErrorIsEnabled())
{
TD.RetryingSqlCommandDueToSqlError(this.eventTraceActivity, exception.Number.ToString(CultureInfo.InvariantCulture));
}
}
catch (InvalidOperationException)
{
if (!this.CheckRetryCount())
{
throw;
}
}
if (this.EnqueueRetry())
{
return;
}
}
if (this.HasOperationTimedOut())
{
if (TD.TimeoutOpeningSqlConnectionIsEnabled())
{
TD.TimeoutOpeningSqlConnection(this.eventTraceActivity, this.timeoutHelper.OriginalTimeout.ToString());
}
}
else
{
if (TD.MaximumRetriesExceededForSqlCommandIsEnabled())
{
TD.MaximumRetriesExceededForSqlCommand(this.eventTraceActivity);
}
}
this.Complete(synchronous, new TimeoutException(SR.TimeoutOnSqlOperation(this.timeoutHelper.OriginalTimeout.ToString())));
}
class RetryErrorCode
{
public RetryErrorCode(int code, RetryErrorOptions retryErrorOptions)
{
this.ErrorCode = code;
this.RetryErrorOptions = retryErrorOptions;
}
public int ErrorCode
{
get;
private set;
}
public RetryErrorOptions RetryErrorOptions
{
get;
private set;
}
}
}
}
|