//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using Microsoft.Data.SqlClient;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace Microsoft.SqlTools.CoreServices.Connection.ReliableConnection
{
internal abstract partial class RetryPolicy
{
///
/// Implements an object holding the decoded reason code returned from SQL Azure when encountering throttling conditions.
///
[Serializable]
public class ThrottlingReason
{
///
/// Returns the error number that corresponds to throttling conditions reported by SQL Azure.
///
public const int ThrottlingErrorNumber = 40501;
///
/// Gets an unknown throttling condition in the event the actual throttling condition cannot be determined.
///
public static ThrottlingReason Unknown
{
get
{
var unknownCondition = new ThrottlingReason() { ThrottlingMode = ThrottlingMode.Unknown };
unknownCondition.throttledResources.Add(Tuple.Create(ThrottledResourceType.Unknown, ThrottlingType.Unknown));
return unknownCondition;
}
}
///
/// Maintains a collection of key-value pairs where a key is resource type and a value is the type of throttling applied to the given resource type.
///
private readonly IList> throttledResources = new List>(9);
///
/// Provides a compiled regular expression used for extracting the reason code from the error message.
///
private static readonly Regex sqlErrorCodeRegEx = new Regex(@"Code:\s*(\d+)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
///
/// Gets the value that reflects the throttling mode in SQL Azure.
///
public ThrottlingMode ThrottlingMode
{
get;
private set;
}
///
/// Gets the list of resources in SQL Azure that were subject to throttling conditions.
///
public IEnumerable> ThrottledResources
{
get
{
return this.throttledResources;
}
}
///
/// Determines throttling conditions from the specified SQL exception.
///
/// The object containing information relevant to an error returned by SQL Server when encountering throttling conditions.
/// An instance of the object holding the decoded reason codes returned from SQL Azure upon encountering throttling conditions.
public static ThrottlingReason FromException(SqlException ex)
{
if (ex != null)
{
foreach (SqlError error in ex.Errors)
{
if (error.Number == ThrottlingErrorNumber)
{
return FromError(error);
}
}
}
return Unknown;
}
///
/// Determines the throttling conditions from the specified SQL error.
///
/// The object containing information relevant to a warning or error returned by SQL Server.
/// An instance of the object holding the decoded reason codes returned from SQL Azure when encountering throttling conditions.
public static ThrottlingReason FromError(SqlError error)
{
if (error != null)
{
var match = sqlErrorCodeRegEx.Match(error.Message);
int reasonCode = 0;
if (match.Success && Int32.TryParse(match.Groups[1].Value, out reasonCode))
{
return FromReasonCode(reasonCode);
}
}
return Unknown;
}
///
/// Determines the throttling conditions from the specified reason code.
///
/// The reason code returned by SQL Azure which contains the throttling mode and the exceeded resource types.
/// An instance of the object holding the decoded reason codes returned from SQL Azure when encountering throttling conditions.
public static ThrottlingReason FromReasonCode(int reasonCode)
{
if (reasonCode > 0)
{
// Decode throttling mode from the last 2 bits.
ThrottlingMode throttlingMode = (ThrottlingMode)(reasonCode & 3);
var condition = new ThrottlingReason() { ThrottlingMode = throttlingMode };
// Shift 8 bits to truncate throttling mode.
int groupCode = reasonCode >> 8;
// Determine throttling type for all well-known resources that may be subject to throttling conditions.
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.PhysicalDatabaseSpace, (ThrottlingType)(groupCode & 3)));
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.PhysicalLogSpace, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.LogWriteIODelay, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.DataReadIODelay, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.CPU, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.DatabaseSize, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.Internal, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.WorkerThreads, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.Internal, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
return condition;
}
else
{
return Unknown;
}
}
///
/// Gets a value indicating whether physical data file space throttling was reported by SQL Azure.
///
public bool IsThrottledOnDataSpace
{
get
{
return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.PhysicalDatabaseSpace).Count() > 0;
}
}
///
/// Gets a value indicating whether physical log space throttling was reported by SQL Azure.
///
public bool IsThrottledOnLogSpace
{
get
{
return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.PhysicalLogSpace).Count() > 0;
}
}
///
/// Gets a value indicating whether transaction activity throttling was reported by SQL Azure.
///
public bool IsThrottledOnLogWrite
{
get { return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.LogWriteIODelay).Count() > 0; }
}
///
/// Gets a value indicating whether data read activity throttling was reported by SQL Azure.
///
public bool IsThrottledOnDataRead
{
get { return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.DataReadIODelay).Count() > 0; }
}
///
/// Gets a value indicating whether CPU throttling was reported by SQL Azure.
///
public bool IsThrottledOnCPU
{
get { return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.CPU).Count() > 0; }
}
///
/// Gets a value indicating whether database size throttling was reported by SQL Azure.
///
public bool IsThrottledOnDatabaseSize
{
get { return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.DatabaseSize).Count() > 0; }
}
///
/// Gets a value indicating whether concurrent requests throttling was reported by SQL Azure.
///
public bool IsThrottledOnWorkerThreads
{
get { return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.WorkerThreads).Count() > 0; }
}
///
/// Gets a value indicating whether throttling conditions were not determined with certainty.
///
public bool IsUnknown
{
get { return ThrottlingMode == ThrottlingMode.Unknown; }
}
///
/// Returns a textual representation the current ThrottlingReason object including the information held with respect to throttled resources.
///
/// A string that represents the current ThrottlingReason object.
public override string ToString()
{
StringBuilder result = new StringBuilder();
result.AppendFormat(Resources.Mode, ThrottlingMode);
var resources = this.throttledResources.Where(x => x.Item1 != ThrottledResourceType.Internal).
Select, string>(x => String.Format(CultureInfo.CurrentCulture, Resources.ThrottlingTypeInfo, x.Item1, x.Item2)).
OrderBy(x => x).ToArray();
result.Append(String.Join(", ", resources));
return result.ToString();
}
}
#region ThrottlingMode enumeration
///
/// Defines the possible throttling modes in SQL Azure.
///
public enum ThrottlingMode
{
///
/// Corresponds to "No Throttling" throttling mode whereby all SQL statements can be processed.
///
NoThrottling = 0,
///
/// Corresponds to "Reject Update / Insert" throttling mode whereby SQL statements such as INSERT, UPDATE, CREATE TABLE and CREATE INDEX are rejected.
///
RejectUpdateInsert = 1,
///
/// Corresponds to "Reject All Writes" throttling mode whereby SQL statements such as INSERT, UPDATE, DELETE, CREATE, DROP are rejected.
///
RejectAllWrites = 2,
///
/// Corresponds to "Reject All" throttling mode whereby all SQL statements are rejected.
///
RejectAll = 3,
///
/// Corresponds to an unknown throttling mode whereby throttling mode cannot be determined with certainty.
///
Unknown = -1
}
#endregion
#region ThrottlingType enumeration
///
/// Defines the possible throttling types in SQL Azure.
///
public enum ThrottlingType
{
///
/// Indicates that no throttling was applied to a given resource.
///
None = 0,
///
/// Corresponds to a Soft throttling type. Soft throttling is applied when machine resources such as, CPU, IO, storage, and worker threads exceed
/// predefined safety thresholds despite the load balancer’s best efforts.
///
Soft = 1,
///
/// Corresponds to a Hard throttling type. Hard throttling is applied when the machine is out of resources, for example storage space.
/// With hard throttling, no new connections are allowed to the databases hosted on the machine until resources are freed up.
///
Hard = 2,
///
/// Corresponds to an unknown throttling type in the event when the throttling type cannot be determined with certainty.
///
Unknown = 3
}
#endregion
#region ThrottledResourceType enumeration
///
/// Defines the types of resources in SQL Azure which may be subject to throttling conditions.
///
public enum ThrottledResourceType
{
///
/// Corresponds to "Physical Database Space" resource which may be subject to throttling.
///
PhysicalDatabaseSpace = 0,
///
/// Corresponds to "Physical Log File Space" resource which may be subject to throttling.
///
PhysicalLogSpace = 1,
///
/// Corresponds to "Transaction Log Write IO Delay" resource which may be subject to throttling.
///
LogWriteIODelay = 2,
///
/// Corresponds to "Database Read IO Delay" resource which may be subject to throttling.
///
DataReadIODelay = 3,
///
/// Corresponds to "CPU" resource which may be subject to throttling.
///
CPU = 4,
///
/// Corresponds to "Database Size" resource which may be subject to throttling.
///
DatabaseSize = 5,
///
/// Corresponds to "SQL Worker Thread Pool" resource which may be subject to throttling.
///
WorkerThreads = 7,
///
/// Corresponds to an internal resource which may be subject to throttling.
///
Internal = 6,
///
/// Corresponds to an unknown resource type in the event when the actual resource cannot be determined with certainty.
///
Unknown = -1
}
#endregion
}
}