From db89c4d2f88f284011534300f859067cf21cc293 Mon Sep 17 00:00:00 2001 From: Adam McCoy Date: Sat, 16 Apr 2022 16:53:49 +1000 Subject: [PATCH 1/5] Tidy up --- source/Nevermore/Transient/RetryPolicy.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/Nevermore/Transient/RetryPolicy.cs b/source/Nevermore/Transient/RetryPolicy.cs index 4cae4e25..1283eefb 100644 --- a/source/Nevermore/Transient/RetryPolicy.cs +++ b/source/Nevermore/Transient/RetryPolicy.cs @@ -15,7 +15,7 @@ namespace Nevermore.Transient /// Initializes a new instance of the class with the specified number of retry attempts and parameters defining the progressive delay between retries. /// /// The strategy to use for this retry policy. - public RetryPolicy(RetryStrategy retryStrategy) : base((default(T) == null) ? Activator.CreateInstance() : default(T), retryStrategy) + public RetryPolicy(RetryStrategy retryStrategy) : base(new T(), retryStrategy) { } @@ -23,7 +23,7 @@ namespace Nevermore.Transient /// Initializes a new instance of the class with the specified number of retry attempts and the default fixed time interval between retries. /// /// The number of retry attempts. - public RetryPolicy(int retryCount) : base((default(T) == null) ? Activator.CreateInstance() : default(T), retryCount) + public RetryPolicy(int retryCount) : base(new T(), retryCount) { } @@ -32,7 +32,7 @@ namespace Nevermore.Transient /// /// The number of retry attempts. /// The interval between retries. - public RetryPolicy(int retryCount, TimeSpan retryInterval) : base((default(T) == null) ? Activator.CreateInstance() : default(T), retryCount, retryInterval) + public RetryPolicy(int retryCount, TimeSpan retryInterval) : base(new T(), retryCount, retryInterval) { } @@ -43,7 +43,7 @@ namespace Nevermore.Transient /// The minimum backoff time. /// The maximum backoff time. /// The time value that will be used to calculate a random delta in the exponential delay between retries. - public RetryPolicy(int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff) : base((default(T) == null) ? Activator.CreateInstance() : default(T), retryCount, minBackoff, maxBackoff, deltaBackoff) + public RetryPolicy(int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff) : base(new T(), retryCount, minBackoff, maxBackoff, deltaBackoff) { } @@ -53,7 +53,7 @@ namespace Nevermore.Transient /// The number of retry attempts. /// The initial interval that will apply for the first retry. /// The incremental time value that will be used to calculate the progressive delay between retries. - public RetryPolicy(int retryCount, TimeSpan initialInterval, TimeSpan increment) : base((default(T) == null) ? Activator.CreateInstance() : default(T), retryCount, initialInterval, increment) + public RetryPolicy(int retryCount, TimeSpan initialInterval, TimeSpan increment) : base(new T(), retryCount, initialInterval, increment) { } } From f39413c5f8c14fcf952eac03d7d90b8e6e5c800b Mon Sep 17 00:00:00 2001 From: Adam McCoy Date: Sat, 16 Apr 2022 16:54:33 +1000 Subject: [PATCH 2/5] Add trace events for retry connections --- .../Transient/DbCommandExtensions.cs | 102 ++++++++++++++++-- 1 file changed, 94 insertions(+), 8 deletions(-) diff --git a/source/Nevermore/Transient/DbCommandExtensions.cs b/source/Nevermore/Transient/DbCommandExtensions.cs index 4336f1d0..bacde7ac 100644 --- a/source/Nevermore/Transient/DbCommandExtensions.cs +++ b/source/Nevermore/Transient/DbCommandExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Data; using System.Data.Common; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; @@ -8,6 +9,8 @@ namespace Nevermore.Transient { internal static class DbCommandExtensions { + static readonly DiagnosticSource RetrySource = new DiagnosticListener("Nevermore.Retry"); + public static int ExecuteNonQueryWithRetry(this DbCommand command, RetryPolicy commandRetryPolicy, RetryPolicy connectionRetryPolicy = null, string operationName = "ExecuteNonQuery") { GuardConnectionIsNotNull(command); @@ -22,7 +25,18 @@ public static int ExecuteNonQueryWithRetry(this DbCommand command, RetryPolicy c finally { if (weOwnTheConnectionLifetime && command.Connection?.State == ConnectionState.Open) + { + if (RetrySource.IsEnabled("ConnectionClosed")) + { + RetrySource.Write("ConnectionClosed", new + { + Command = command, + RetryPolicy = commandRetryPolicy, + OperationName = operationName + }); + } command.Connection.Close(); + } } }); } @@ -41,7 +55,19 @@ public static Task ExecuteNonQueryWithRetryAsync(this DbCommand command, Re finally { if (weOwnTheConnectionLifetime && command.Connection?.State == ConnectionState.Open) + { + if (RetrySource.IsEnabled("ConnectionClosed")) + { + RetrySource.Write("ConnectionClosed", new + { + Command = command, + RetryPolicy = commandRetryPolicy, + OperationName = operationName + }); + } + await command.Connection.CloseAsync(); + } } }); } @@ -57,12 +83,22 @@ public static DbDataReader ExecuteReaderWithRetry(this DbCommand command, RetryP { return command.ExecuteReader(behavior); } - catch (Exception) + finally { - if (weOwnTheConnectionLifetime && command.Connection != null && - command.Connection.State == ConnectionState.Open) + if (weOwnTheConnectionLifetime && command.Connection?.State == ConnectionState.Open) + { + if (RetrySource.IsEnabled("ConnectionClosed")) + { + RetrySource.Write("ConnectionClosed", new + { + Command = command, + RetryPolicy = commandRetryPolicy, + OperationName = operationName + }); + } + command.Connection.Close(); - throw; + } } }); } @@ -79,12 +115,22 @@ public static async Task ExecuteReaderWithRetryAsync(this DbComman { return await command.ExecuteReaderAsync(commandBehavior, cancellationToken); } - catch (Exception) + finally { - if (weOwnTheConnectionLifetime && command.Connection != null && - command.Connection.State == ConnectionState.Open) + if (weOwnTheConnectionLifetime && command.Connection?.State == ConnectionState.Open) + { + if (RetrySource.IsEnabled("ConnectionClosed")) + { + RetrySource.Write("ConnectionClosed", new + { + Command = command, + RetryPolicy = commandRetryPolicy, + OperationName = operationName + }); + } + await command.Connection.CloseAsync(); - throw; + } } }); } @@ -103,7 +149,19 @@ public static object ExecuteScalarWithRetry(this DbCommand command, RetryPolicy finally { if (weOwnTheConnectionLifetime && command.Connection?.State == ConnectionState.Open) + { + if (RetrySource.IsEnabled("ConnectionClosed")) + { + RetrySource.Write("ConnectionClosed", new + { + Command = command, + RetryPolicy = commandRetryPolicy, + OperationName = operationName + }); + } + command.Connection.Close(); + } } }); } @@ -122,7 +180,19 @@ public static async Task ExecuteScalarWithRetryAsync(this DbCommand comm finally { if (weOwnTheConnectionLifetime && command.Connection?.State == ConnectionState.Open) + { + if (RetrySource.IsEnabled("ConnectionClosed")) + { + RetrySource.Write("ConnectionClosed", new + { + Command = command, + RetryPolicy = commandRetryPolicy, + OperationName = operationName + }); + } + await command.Connection.CloseAsync(); + } } }); } @@ -146,6 +216,14 @@ static bool EnsureValidConnection(DbCommand command, RetryPolicy retryPolicy) if (command.Connection.State == ConnectionState.Open) return false; command.Connection.OpenWithRetry(retryPolicy); + if (RetrySource.IsEnabled("ConnectionReopened")) + { + RetrySource.Write("ConnectionReopened", new + { + Command = command, + Policy = retryPolicy + }); + } return true; } @@ -158,6 +236,14 @@ static async Task EnsureValidConnectionAsync(DbCommand command, RetryPolic if (command.Connection.State == ConnectionState.Open) return false; await command.Connection.OpenWithRetryAsync(retryPolicy, cancellationToken); + if (RetrySource.IsEnabled("ConnectionReopened")) + { + RetrySource.Write("ConnectionReopened", new + { + Command = command, + Policy = retryPolicy + }); + } return true; } } From 2da80f281fc5756dce52aa94bfd85bc003a74774 Mon Sep 17 00:00:00 2001 From: Adam McCoy Date: Sat, 16 Apr 2022 20:05:36 +1000 Subject: [PATCH 3/5] Refactor --- .../Diagnostics/Events/DiagnosticSources.cs | 51 +++++++++++ .../Transient/DbCommandExtensions.cs | 87 ++----------------- .../Transient/DbConnectionExtensions.cs | 15 +++- 3 files changed, 71 insertions(+), 82 deletions(-) create mode 100644 source/Nevermore/Diagnostics/Events/DiagnosticSources.cs diff --git a/source/Nevermore/Diagnostics/Events/DiagnosticSources.cs b/source/Nevermore/Diagnostics/Events/DiagnosticSources.cs new file mode 100644 index 00000000..f5aa6134 --- /dev/null +++ b/source/Nevermore/Diagnostics/Events/DiagnosticSources.cs @@ -0,0 +1,51 @@ +using System.Data.Common; +using System.Diagnostics; +using Nevermore.Transient; + +namespace Nevermore.Diagnostics.Events +{ + public static class DiagnosticSources + { + public static class Retry + { + static readonly DiagnosticSource Source = new DiagnosticListener("Nevermore.Retry"); + + public static void OwnedConnectionClosed(DbCommand command, RetryPolicy retryPolicy, string operationName) + { + if (Source.IsEnabled(nameof(OwnedConnectionClosed))) + { + Source.Write(nameof(OwnedConnectionClosed), new + { + Command = command, + RetryPolicy = retryPolicy, + OperationName = operationName + }); + } + } + + public static void ConnectionOpened(DbConnection connection, RetryPolicy retryPolicy) + { + if (Source.IsEnabled(nameof(ConnectionOpened))) + { + Source.Write(nameof(ConnectionOpened), new + { + Connection = connection, + RetryPolicy = retryPolicy + }); + } + } + + public static void ConnectionReopened(DbCommand command, RetryPolicy retryPolicy) + { + if (Source.IsEnabled(nameof(ConnectionReopened))) + { + Source.Write(nameof(ConnectionReopened), new + { + Command = command, + RetryPolicy = retryPolicy + }); + } + } + } + } +} \ No newline at end of file diff --git a/source/Nevermore/Transient/DbCommandExtensions.cs b/source/Nevermore/Transient/DbCommandExtensions.cs index bacde7ac..f300132e 100644 --- a/source/Nevermore/Transient/DbCommandExtensions.cs +++ b/source/Nevermore/Transient/DbCommandExtensions.cs @@ -1,16 +1,14 @@ using System; using System.Data; using System.Data.Common; -using System.Diagnostics; using System.Threading; using System.Threading.Tasks; +using Nevermore.Diagnostics.Events; namespace Nevermore.Transient { internal static class DbCommandExtensions { - static readonly DiagnosticSource RetrySource = new DiagnosticListener("Nevermore.Retry"); - public static int ExecuteNonQueryWithRetry(this DbCommand command, RetryPolicy commandRetryPolicy, RetryPolicy connectionRetryPolicy = null, string operationName = "ExecuteNonQuery") { GuardConnectionIsNotNull(command); @@ -26,16 +24,8 @@ public static int ExecuteNonQueryWithRetry(this DbCommand command, RetryPolicy c { if (weOwnTheConnectionLifetime && command.Connection?.State == ConnectionState.Open) { - if (RetrySource.IsEnabled("ConnectionClosed")) - { - RetrySource.Write("ConnectionClosed", new - { - Command = command, - RetryPolicy = commandRetryPolicy, - OperationName = operationName - }); - } command.Connection.Close(); + DiagnosticSources.Retry.OwnedConnectionClosed(command, commandRetryPolicy, operationName); } } }); @@ -56,17 +46,8 @@ public static Task ExecuteNonQueryWithRetryAsync(this DbCommand command, Re { if (weOwnTheConnectionLifetime && command.Connection?.State == ConnectionState.Open) { - if (RetrySource.IsEnabled("ConnectionClosed")) - { - RetrySource.Write("ConnectionClosed", new - { - Command = command, - RetryPolicy = commandRetryPolicy, - OperationName = operationName - }); - } - await command.Connection.CloseAsync(); + DiagnosticSources.Retry.OwnedConnectionClosed(command, commandRetryPolicy, operationName); } } }); @@ -87,17 +68,8 @@ public static DbDataReader ExecuteReaderWithRetry(this DbCommand command, RetryP { if (weOwnTheConnectionLifetime && command.Connection?.State == ConnectionState.Open) { - if (RetrySource.IsEnabled("ConnectionClosed")) - { - RetrySource.Write("ConnectionClosed", new - { - Command = command, - RetryPolicy = commandRetryPolicy, - OperationName = operationName - }); - } - command.Connection.Close(); + DiagnosticSources.Retry.OwnedConnectionClosed(command, commandRetryPolicy, operationName); } } }); @@ -119,17 +91,8 @@ public static async Task ExecuteReaderWithRetryAsync(this DbComman { if (weOwnTheConnectionLifetime && command.Connection?.State == ConnectionState.Open) { - if (RetrySource.IsEnabled("ConnectionClosed")) - { - RetrySource.Write("ConnectionClosed", new - { - Command = command, - RetryPolicy = commandRetryPolicy, - OperationName = operationName - }); - } - await command.Connection.CloseAsync(); + DiagnosticSources.Retry.OwnedConnectionClosed(command, commandRetryPolicy, operationName); } } }); @@ -150,17 +113,8 @@ public static object ExecuteScalarWithRetry(this DbCommand command, RetryPolicy { if (weOwnTheConnectionLifetime && command.Connection?.State == ConnectionState.Open) { - if (RetrySource.IsEnabled("ConnectionClosed")) - { - RetrySource.Write("ConnectionClosed", new - { - Command = command, - RetryPolicy = commandRetryPolicy, - OperationName = operationName - }); - } - command.Connection.Close(); + DiagnosticSources.Retry.OwnedConnectionClosed(command, commandRetryPolicy, operationName); } } }); @@ -181,17 +135,8 @@ public static async Task ExecuteScalarWithRetryAsync(this DbCommand comm { if (weOwnTheConnectionLifetime && command.Connection?.State == ConnectionState.Open) { - if (RetrySource.IsEnabled("ConnectionClosed")) - { - RetrySource.Write("ConnectionClosed", new - { - Command = command, - RetryPolicy = commandRetryPolicy, - OperationName = operationName - }); - } - await command.Connection.CloseAsync(); + DiagnosticSources.Retry.OwnedConnectionClosed(command, commandRetryPolicy, operationName); } } }); @@ -216,14 +161,7 @@ static bool EnsureValidConnection(DbCommand command, RetryPolicy retryPolicy) if (command.Connection.State == ConnectionState.Open) return false; command.Connection.OpenWithRetry(retryPolicy); - if (RetrySource.IsEnabled("ConnectionReopened")) - { - RetrySource.Write("ConnectionReopened", new - { - Command = command, - Policy = retryPolicy - }); - } + DiagnosticSources.Retry.ConnectionReopened(command, retryPolicy); return true; } @@ -236,14 +174,7 @@ static async Task EnsureValidConnectionAsync(DbCommand command, RetryPolic if (command.Connection.State == ConnectionState.Open) return false; await command.Connection.OpenWithRetryAsync(retryPolicy, cancellationToken); - if (RetrySource.IsEnabled("ConnectionReopened")) - { - RetrySource.Write("ConnectionReopened", new - { - Command = command, - Policy = retryPolicy - }); - } + DiagnosticSources.Retry.ConnectionReopened(command, retryPolicy); return true; } } diff --git a/source/Nevermore/Transient/DbConnectionExtensions.cs b/source/Nevermore/Transient/DbConnectionExtensions.cs index 060bc1f9..8d9ddb41 100644 --- a/source/Nevermore/Transient/DbConnectionExtensions.cs +++ b/source/Nevermore/Transient/DbConnectionExtensions.cs @@ -1,8 +1,7 @@ -using System; -using System.Data; using System.Data.Common; using System.Threading; using System.Threading.Tasks; +using Nevermore.Diagnostics.Events; namespace Nevermore.Transient { @@ -16,7 +15,11 @@ public static void OpenWithRetry(this DbConnection connection) public static void OpenWithRetry(this DbConnection connection, RetryPolicy retryPolicy) { - (retryPolicy ?? RetryPolicy.NoRetry).LoggingRetries("Open Database Connection").ExecuteAction(connection.Open); + (retryPolicy ?? RetryPolicy.NoRetry).LoggingRetries("Open Database Connection").ExecuteAction(() => + { + connection.Open(); + DiagnosticSources.Retry.ConnectionOpened(connection, retryPolicy); + }); } public static Task OpenWithRetryAsync(this DbConnection connection) @@ -36,7 +39,11 @@ public static Task OpenWithRetryAsync(this DbConnection connection, RetryPolicy public static Task OpenWithRetryAsync(this DbConnection connection, RetryPolicy retryPolicy, CancellationToken cancellationToken) { - return (retryPolicy ?? RetryPolicy.NoRetry).LoggingRetries("Open Database Connection").ExecuteActionAsync(() => connection.OpenAsync(cancellationToken)); + return (retryPolicy ?? RetryPolicy.NoRetry).LoggingRetries("Open Database Connection").ExecuteActionAsync(async () => + { + await connection.OpenAsync(cancellationToken); + DiagnosticSources.Retry.ConnectionOpened(connection, retryPolicy); + }); } } } \ No newline at end of file From a3429d4aaf0147a5c08ea1b1220906c67a73e91d Mon Sep 17 00:00:00 2001 From: Adam McCoy Date: Sat, 16 Apr 2022 21:10:37 +1000 Subject: [PATCH 4/5] Add connection re-opening event --- .../Diagnostics/Events/DiagnosticSources.cs | 12 ++++++++++++ source/Nevermore/Transient/DbCommandExtensions.cs | 2 ++ 2 files changed, 14 insertions(+) diff --git a/source/Nevermore/Diagnostics/Events/DiagnosticSources.cs b/source/Nevermore/Diagnostics/Events/DiagnosticSources.cs index f5aa6134..426bd62c 100644 --- a/source/Nevermore/Diagnostics/Events/DiagnosticSources.cs +++ b/source/Nevermore/Diagnostics/Events/DiagnosticSources.cs @@ -35,6 +35,18 @@ public static void ConnectionOpened(DbConnection connection, RetryPolicy retryPo } } + public static void ConnectionReopening(DbCommand command, RetryPolicy retryPolicy) + { + if (Source.IsEnabled(nameof(ConnectionReopening))) + { + Source.Write(nameof(ConnectionReopening), new + { + Command = command, + RetryPolicy = retryPolicy + }); + } + } + public static void ConnectionReopened(DbCommand command, RetryPolicy retryPolicy) { if (Source.IsEnabled(nameof(ConnectionReopened))) diff --git a/source/Nevermore/Transient/DbCommandExtensions.cs b/source/Nevermore/Transient/DbCommandExtensions.cs index f300132e..8414e9d9 100644 --- a/source/Nevermore/Transient/DbCommandExtensions.cs +++ b/source/Nevermore/Transient/DbCommandExtensions.cs @@ -160,6 +160,7 @@ static bool EnsureValidConnection(DbCommand command, RetryPolicy retryPolicy) if (command.Connection.State == ConnectionState.Open) return false; + DiagnosticSources.Retry.ConnectionReopening(command, retryPolicy); command.Connection.OpenWithRetry(retryPolicy); DiagnosticSources.Retry.ConnectionReopened(command, retryPolicy); return true; @@ -173,6 +174,7 @@ static async Task EnsureValidConnectionAsync(DbCommand command, RetryPolic if (command.Connection.State == ConnectionState.Open) return false; + DiagnosticSources.Retry.ConnectionReopening(command, retryPolicy); await command.Connection.OpenWithRetryAsync(retryPolicy, cancellationToken); DiagnosticSources.Retry.ConnectionReopened(command, retryPolicy); return true; From 64bf7adf3695ddd0670f790f66379e4780695d88 Mon Sep 17 00:00:00 2001 From: Adam McCoy Date: Mon, 18 Apr 2022 23:42:38 +1000 Subject: [PATCH 5/5] Fix broken readers --- source/Nevermore/Transient/DbCommandExtensions.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/source/Nevermore/Transient/DbCommandExtensions.cs b/source/Nevermore/Transient/DbCommandExtensions.cs index 8414e9d9..1c7b1462 100644 --- a/source/Nevermore/Transient/DbCommandExtensions.cs +++ b/source/Nevermore/Transient/DbCommandExtensions.cs @@ -64,13 +64,15 @@ public static DbDataReader ExecuteReaderWithRetry(this DbCommand command, RetryP { return command.ExecuteReader(behavior); } - finally + catch (Exception) { if (weOwnTheConnectionLifetime && command.Connection?.State == ConnectionState.Open) { command.Connection.Close(); DiagnosticSources.Retry.OwnedConnectionClosed(command, commandRetryPolicy, operationName); } + + throw; } }); } @@ -87,13 +89,15 @@ public static async Task ExecuteReaderWithRetryAsync(this DbComman { return await command.ExecuteReaderAsync(commandBehavior, cancellationToken); } - finally + catch (Exception) { if (weOwnTheConnectionLifetime && command.Connection?.State == ConnectionState.Open) { await command.Connection.CloseAsync(); DiagnosticSources.Retry.OwnedConnectionClosed(command, commandRetryPolicy, operationName); } + + throw; } }); }