-
Notifications
You must be signed in to change notification settings - Fork 316
Configurable Retry Logic - Preview 1 #693
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d86ed3a
185c8cd
f4e1a24
366c21a
9b344fc
f0ada60
eab4106
9909fe5
868e576
8c6d8f8
b653999
ee837b6
bc09f5f
cd9b42d
da06ca9
d8a556c
ccfbdc1
83182d6
e6ba4ed
dbd7a4b
f8c274f
54b8eb8
91ec47c
c362aa8
5b6c70f
8299116
738da14
415cc7f
d385863
f6a417d
fcc5890
8ab901b
9cd227b
0194460
54afb7c
451118d
85a848e
d64e43d
fe75ca9
dc6eda7
b181059
4b567bc
03c4f4a
ad11253
087fa76
0164582
7760254
738ab94
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| using System; | ||
| // <Snippet1> | ||
| using Microsoft.Data.SqlClient; | ||
|
|
||
| /// Detecting retriable exceptions is a vital part of the retry pattern. | ||
| /// Before applying retry logic it is important to investigate exceptions and choose a retry provider that best fits your scenario. | ||
| /// First, log your exceptions and find transient faults. | ||
| /// The purpose of this sample is to illustrate how to use this feature and the condition might not be realistic. | ||
| class RetryLogicSample | ||
| { | ||
| private const string DefaultDB = "Northwind"; | ||
| private const string CnnStringFormat = "Server=localhost; Initial Catalog={0}; Integrated Security=true; pooling=false;"; | ||
| private const string DropDatabaseFormat = "DROP DATABASE {0}"; | ||
|
|
||
| // For general use | ||
| private static SqlConnection s_generalConnection = new SqlConnection(string.Format(CnnStringFormat, DefaultDB)); | ||
|
|
||
| static void Main(string[] args) | ||
| { | ||
| // 1. Define the retry logic parameters | ||
| var options = new SqlRetryLogicOption() | ||
| { | ||
| NumberOfTries = 5, | ||
| MaxTimeInterval = TimeSpan.FromSeconds(20), | ||
| DeltaTime = TimeSpan.FromSeconds(1) | ||
| }; | ||
|
|
||
| // 2. Create a retry provider | ||
| var provider = SqlConfigurableRetryFactory.CreateExponentialRetryProvider(options); | ||
|
|
||
| // define the retrying event to report the execution attempts | ||
| provider.Retrying += (object s, SqlRetryingEventArgs e) => | ||
| { | ||
| int attempts = e.RetryCount + 1; | ||
| Console.ForegroundColor = ConsoleColor.Yellow; | ||
| Console.WriteLine($"attempt {attempts} - current delay time:{e.Delay} \n"); | ||
| Console.ForegroundColor = ConsoleColor.DarkGray; | ||
| if (e.Exceptions[e.Exceptions.Count - 1] is SqlException ex) | ||
| { | ||
| Console.WriteLine($"{ex.Number}-{ex.Message}\n"); | ||
| } | ||
| else | ||
| { | ||
| Console.WriteLine($"{e.Exceptions[e.Exceptions.Count - 1].Message}\n"); | ||
| } | ||
|
|
||
| // It is not a good practice to do time-consuming tasks inside the retrying event which blocks the running task. | ||
| // Use parallel programming patterns to mitigate it. | ||
| if (e.RetryCount == provider.RetryLogic.NumberOfTries - 1) | ||
| { | ||
| Console.WriteLine("This is the last chance to execute the command before throwing the exception."); | ||
| Console.WriteLine("Press Enter when you're ready:"); | ||
| Console.ReadLine(); | ||
| Console.WriteLine("continue ..."); | ||
| } | ||
| }; | ||
|
|
||
| // Open the general connection. | ||
| s_generalConnection.Open(); | ||
|
|
||
| try | ||
| { | ||
| // Assume the database is being created and other services are going to connect to it. | ||
| RetryConnection(provider); | ||
| } | ||
| catch | ||
| { | ||
| // exception is thrown if connecting to the database isn't successful. | ||
| throw; | ||
| } | ||
| } | ||
|
|
||
| private static void ExecuteCommand(SqlConnection cn, string command) | ||
| { | ||
| using var cmd = cn.CreateCommand(); | ||
| cmd.CommandText = command; | ||
| cmd.ExecuteNonQuery(); | ||
| } | ||
|
|
||
| private static void RetryConnection(SqlRetryLogicBaseProvider provider) | ||
| { | ||
| // Change this if you already have a database with the same name in your database. | ||
| string dbName = "Invalid_DB_Open"; | ||
|
|
||
| // Create a connection to an invalid database. | ||
| using var cnn = new SqlConnection(string.Format(CnnStringFormat, dbName)); | ||
| // 3. Assign the `provider` to the connection | ||
| cnn.RetryLogicProvider = provider; | ||
| Console.WriteLine($"Connecting to the [{dbName}] ..."); | ||
| // Manually execute the following command in SSMS to create the invalid database while the SqlConnection is attempting to connect to it. | ||
| // >> CREATE DATABASE Invalid_DB_Open; | ||
| Console.WriteLine($"Manually, run the 'CREATE DATABASE {dbName};' in the SQL Server before exceeding the {provider.RetryLogic.NumberOfTries} attempts."); | ||
| // the connection tries to connect to the database 5 times | ||
| Console.WriteLine("The first attempt, before getting into the retry logic."); | ||
| cnn.Open(); | ||
| Console.WriteLine($"Connected to the [{dbName}] successfully."); | ||
|
|
||
| cnn.Close(); | ||
|
|
||
| // Drop it after test | ||
| ExecuteCommand(s_generalConnection, string.Format(DropDatabaseFormat, dbName)); | ||
| Console.WriteLine($"The [{dbName}] is removed."); | ||
| } | ||
| } | ||
| // </Snippet1> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,245 @@ | ||
| using System; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.Data.SqlClient; | ||
|
|
||
| class RetryLogicSample | ||
| { | ||
| // <Snippet1> | ||
| /// Detecting retriable exceptions is a vital part of the retry pattern. | ||
| /// Before applying retry logic it is important to investigate exceptions and choose a retry provider that best fits your scenario. | ||
| /// First, log your exceptions and find transient faults. | ||
| /// The purpose of this sample is to illustrate how to use this feature and the condition might not be realistic. | ||
|
|
||
| private const string DefaultDB = "Northwind"; | ||
| private const string CnnStringFormat = "Server=localhost; Initial Catalog={0}; Integrated Security=true; pooling=false;"; | ||
| private const string DropDatabaseFormat = "DROP DATABASE {0}"; | ||
| private const string CreateDatabaseFormat = "CREATE DATABASE {0}"; | ||
|
|
||
| // For general use | ||
| private static SqlConnection s_generalConnection = new SqlConnection(string.Format(CnnStringFormat, DefaultDB)); | ||
|
|
||
| static void Main(string[] args) | ||
| { | ||
| // 1. Define the retry logic parameters | ||
| var options = new SqlRetryLogicOption() | ||
| { | ||
| NumberOfTries = 5, | ||
| MaxTimeInterval = TimeSpan.FromSeconds(20), | ||
| DeltaTime = TimeSpan.FromSeconds(1), | ||
| AuthorizedSqlCondition = null, | ||
| // error number 3702 : Cannot drop database "xxx" because it is currently in use. | ||
| TransientErrors = new int[] {3702} | ||
| }; | ||
|
|
||
| // 2. Create a retry provider | ||
| var provider = SqlConfigurableRetryFactory.CreateExponentialRetryProvider(options); | ||
|
|
||
| // define the retrying event to report execution attempts | ||
| provider.Retrying += (object s, SqlRetryingEventArgs e) => | ||
| { | ||
| int attempts = e.RetryCount + 1; | ||
| Console.ForegroundColor = ConsoleColor.Yellow; | ||
| Console.WriteLine($"attempt {attempts} - current delay time:{e.Delay} \n"); | ||
| Console.ForegroundColor = ConsoleColor.DarkGray; | ||
| if (e.Exceptions[e.Exceptions.Count - 1] is SqlException ex) | ||
| { | ||
| Console.WriteLine($"{ex.Number}-{ex.Message}\n"); | ||
| } | ||
| else | ||
| { | ||
| Console.WriteLine($"{e.Exceptions[e.Exceptions.Count - 1].Message}\n"); | ||
| } | ||
|
|
||
| // It is not good practice to do time-consuming tasks inside the retrying event which blocks the running task. | ||
| // Use parallel programming patterns to mitigate it. | ||
| if (e.RetryCount == provider.RetryLogic.NumberOfTries - 1) | ||
| { | ||
| Console.WriteLine("This is the last chance to execute the command before throwing the exception."); | ||
| Console.WriteLine("Press Enter when you're ready:"); | ||
| Console.ReadLine(); | ||
| Console.WriteLine("continue ..."); | ||
| } | ||
| }; | ||
|
|
||
| // Open a general connection. | ||
| s_generalConnection.Open(); | ||
|
|
||
| try | ||
| { | ||
| // Assume the database is creating and other services are going to connect to it. | ||
| RetryCommand(provider); | ||
| } | ||
| catch | ||
| { | ||
| s_generalConnection.Close(); | ||
| // exception is thrown if connecting to the database isn't successful. | ||
| throw; | ||
| } | ||
| s_generalConnection.Close(); | ||
| } | ||
|
|
||
| private static void ExecuteCommand(SqlConnection cn, string command) | ||
| { | ||
| using var cmd = cn.CreateCommand(); | ||
| cmd.CommandText = command; | ||
| cmd.ExecuteNonQuery(); | ||
| } | ||
|
|
||
| private static void FindActiveSessions(SqlConnection cnn, string dbName) | ||
| { | ||
| using var cmd = cnn.CreateCommand(); | ||
| cmd.CommandText = "DECLARE @query NVARCHAR(max) = '';" + Environment.NewLine + | ||
| $"SELECT @query = @query + 'KILL ' + CAST(spid as varchar(50)) + ';' FROM sys.sysprocesses WHERE dbid = DB_ID('{dbName}')" + Environment.NewLine + | ||
| "SELECT @query AS Active_sessions;"; | ||
| var reader = cmd.ExecuteReader(); | ||
| if (reader.Read()) | ||
| { | ||
| Console.ForegroundColor = ConsoleColor.Green; | ||
| Console.Write($">> Execute the '{reader.GetString(0)}' command in SQL Server to unblock the running task."); | ||
| Console.ResetColor(); | ||
| } | ||
| reader.Close(); | ||
| } | ||
| // </Snippet1> | ||
| // <Snippet2> | ||
| private static void RetryCommand(SqlRetryLogicBaseProvider provider) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add some comments to distinguish these There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can find the description in the related SqlCommand.xml file. There is a common explanation for this series of samples. Feel free to suggest here If you need something more. |
||
| { | ||
| // Change this if you already have a database with the same name in your database. | ||
| string dbName = "RetryCommand_TestDatabase"; | ||
|
|
||
| // Subscribe a new event on retry event and discover the active sessions on a database | ||
| EventHandler<SqlRetryingEventArgs> retryEvent = (object s, SqlRetryingEventArgs e) => | ||
| { | ||
| // Run just at first execution | ||
| if (e.RetryCount == 1) | ||
| { | ||
| FindActiveSessions(s_generalConnection, dbName); | ||
| Console.WriteLine($"Before exceeding {provider.RetryLogic.NumberOfTries} attempts."); | ||
| } | ||
| }; | ||
|
|
||
| provider.Retrying += retryEvent; | ||
|
|
||
| // Create a new database. | ||
| ExecuteCommand(s_generalConnection, string.Format(CreateDatabaseFormat, dbName)); | ||
| Console.WriteLine($"The '{dbName}' database is created."); | ||
|
|
||
| // Open a connection to the newly created database to block it from being dropped. | ||
| using var blockingCnn = new SqlConnection(string.Format(CnnStringFormat, dbName)); | ||
| blockingCnn.Open(); | ||
| Console.WriteLine($"Established a connection to '{dbName}' to block it from being dropped."); | ||
|
|
||
| Console.WriteLine($"Dropping `{dbName}`..."); | ||
| // Try to drop the new database. | ||
| RetryCommandSync(provider, dbName); | ||
|
|
||
| Console.WriteLine("Command executed successfully."); | ||
|
|
||
| provider.Retrying -= retryEvent; | ||
| } | ||
|
|
||
| private static void RetryCommandSync(SqlRetryLogicBaseProvider provider, string dbName) | ||
| { | ||
| using var cmd = s_generalConnection.CreateCommand(); | ||
| cmd.CommandText = string.Format(DropDatabaseFormat, dbName); | ||
| // 3. Assign the `provider` to the command | ||
| cmd.RetryLogicProvider = provider; | ||
| Console.WriteLine("The first attempt, before getting into the retry logic."); | ||
| cmd.ExecuteNonQuery(); | ||
| } | ||
| // </Snippet2> | ||
| // <Snippet3> | ||
| private static void RetryCommand(SqlRetryLogicBaseProvider provider) | ||
| { | ||
| // Change this if you already have a database with the same name in your database. | ||
| string dbName = "RetryCommand_TestDatabase"; | ||
|
|
||
| // Subscribe to the retry event and discover active sessions in a database | ||
| EventHandler<SqlRetryingEventArgs> retryEvent = (object s, SqlRetryingEventArgs e) => | ||
| { | ||
| // Run just at first execution | ||
| if (e.RetryCount == 1) | ||
| { | ||
| FindActiveSessions(s_generalConnection, dbName); | ||
| Console.WriteLine($"Before exceeding {provider.RetryLogic.NumberOfTries} attempts."); | ||
| } | ||
| }; | ||
|
|
||
| provider.Retrying += retryEvent; | ||
|
|
||
| // Create a new database. | ||
| ExecuteCommand(s_generalConnection, string.Format(CreateDatabaseFormat, dbName)); | ||
| Console.WriteLine($"The '{dbName}' database is created."); | ||
|
|
||
| // Open a connection to the newly created database to block it from being dropped. | ||
| using var blockingCnn = new SqlConnection(string.Format(CnnStringFormat, dbName)); | ||
| blockingCnn.Open(); | ||
| Console.WriteLine($"Established a connection to '{dbName}' to block it from being dropped."); | ||
|
|
||
| Console.WriteLine("Dropping the database..."); | ||
| // Try to drop the new database. | ||
| RetryCommandAsync(provider, dbName).Wait(); | ||
|
|
||
| Console.WriteLine("Command executed successfully."); | ||
|
|
||
| provider.Retrying -= retryEvent; | ||
| } | ||
|
|
||
| private static async Task RetryCommandAsync(SqlRetryLogicBaseProvider provider, string dbName) | ||
| { | ||
| using var cmd = s_generalConnection.CreateCommand(); | ||
| cmd.CommandText = string.Format(DropDatabaseFormat, dbName); | ||
| // 3. Assign the `provider` to the command | ||
| cmd.RetryLogicProvider = provider; | ||
| Console.WriteLine("The first attempt, before getting into the retry logic."); | ||
| await cmd.ExecuteNonQueryAsync(); | ||
| } | ||
| // </Snippet3> | ||
| // <Snippet4> | ||
| private static void RetryCommand(SqlRetryLogicBaseProvider provider) | ||
| { | ||
| // Change this if you already have a database with the same name in your database. | ||
| string dbName = "RetryCommand_TestDatabase"; | ||
|
|
||
| // Subscribe to the retry event and discover the active sessions in a database | ||
| EventHandler<SqlRetryingEventArgs> retryEvent = (object s, SqlRetryingEventArgs e) => | ||
| { | ||
| // Run just at first execution | ||
| if (e.RetryCount == 1) | ||
| { | ||
| FindActiveSessions(s_generalConnection, dbName); | ||
| Console.WriteLine($"Before exceeding {provider.RetryLogic.NumberOfTries} attempts."); | ||
| } | ||
| }; | ||
|
|
||
| provider.Retrying += retryEvent; | ||
|
|
||
| // Create a new database. | ||
| ExecuteCommand(s_generalConnection, string.Format(CreateDatabaseFormat, dbName)); | ||
| Console.WriteLine($"The '{dbName}' database is created."); | ||
|
|
||
| // Open a connection to the newly created database to block it from being dropped. | ||
| using var blockingCnn = new SqlConnection(string.Format(CnnStringFormat, dbName)); | ||
| blockingCnn.Open(); | ||
| Console.WriteLine($"Established a connection to '{dbName}' to block it from being dropped."); | ||
|
|
||
| Console.WriteLine("Dropping the database..."); | ||
| // Try to drop the new database. | ||
| RetryCommandBeginExecuteAsync(provider, dbName).Wait(); | ||
|
|
||
| Console.WriteLine("Command executed successfully."); | ||
|
|
||
| provider.Retrying -= retryEvent; | ||
| } | ||
|
|
||
| private static async Task RetryCommandBeginExecuteAsync(SqlRetryLogicBaseProvider provider, string dbName) | ||
| { | ||
| using var cmd = s_generalConnection.CreateCommand(); | ||
| cmd.CommandText = string.Format(DropDatabaseFormat, dbName); | ||
| // Execute the BeginExecuteXXX and EndExecuteXXX functions by using Task.Factory.FromAsync(). | ||
| // Apply the retry logic by using the ExecuteAsync function of the configurable retry logic provider. | ||
| Console.WriteLine("The first attempt, before getting into the retry logic."); | ||
| await provider.ExecuteAsync(cmd, () => Task.Factory.FromAsync(cmd.BeginExecuteNonQuery(), cmd.EndExecuteNonQuery)); | ||
| } | ||
| // </Snippet4> | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| using System; | ||
| using System.Text.RegularExpressions; | ||
| using Microsoft.Data.SqlClient; | ||
|
|
||
| class RetryLogicSample | ||
| { | ||
| static void Main(string[] args) | ||
| { | ||
| // <Snippet1> | ||
| var RetryLogicOption = new SqlRetryLogicOption() | ||
| { | ||
| NumberOfTries = 5, | ||
| // Declare the error number 102 as a transient error to apply the retry logic when it occurs. | ||
| TransientErrors = new int[] { 102 }, | ||
| // When a SqlCommand executes out of a transaction, | ||
| // the retry logic will apply if it contains a 'select' keyword. | ||
| AuthorizedSqlCondition = x => string.IsNullOrEmpty(x) | ||
| || Regex.IsMatch(x, @"\b(SELECT)\b", RegexOptions.IgnoreCase), | ||
| DeltaTime = TimeSpan.FromSeconds(1), | ||
| MaxTimeInterval = TimeSpan.FromSeconds(60), | ||
| MinTimeInterval = TimeSpan.FromSeconds(3) | ||
| }; | ||
| // </Snippet1> | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.