// // 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 } }