diff --git a/source/Nevermore/Diagnostics/Events/DiagnosticSources.cs b/source/Nevermore/Diagnostics/Events/DiagnosticSources.cs new file mode 100644 index 00000000..426bd62c --- /dev/null +++ b/source/Nevermore/Diagnostics/Events/DiagnosticSources.cs @@ -0,0 +1,63 @@ +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 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))) + { + 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 4336f1d0..1c7b1462 100644 --- a/source/Nevermore/Transient/DbCommandExtensions.cs +++ b/source/Nevermore/Transient/DbCommandExtensions.cs @@ -3,6 +3,7 @@ using System.Data.Common; using System.Threading; using System.Threading.Tasks; +using Nevermore.Diagnostics.Events; namespace Nevermore.Transient { @@ -22,7 +23,10 @@ public static int ExecuteNonQueryWithRetry(this DbCommand command, RetryPolicy c finally { if (weOwnTheConnectionLifetime && command.Connection?.State == ConnectionState.Open) + { command.Connection.Close(); + DiagnosticSources.Retry.OwnedConnectionClosed(command, commandRetryPolicy, operationName); + } } }); } @@ -41,7 +45,10 @@ public static Task ExecuteNonQueryWithRetryAsync(this DbCommand command, Re finally { if (weOwnTheConnectionLifetime && command.Connection?.State == ConnectionState.Open) + { await command.Connection.CloseAsync(); + DiagnosticSources.Retry.OwnedConnectionClosed(command, commandRetryPolicy, operationName); + } } }); } @@ -59,9 +66,12 @@ public static DbDataReader ExecuteReaderWithRetry(this DbCommand command, RetryP } catch (Exception) { - if (weOwnTheConnectionLifetime && command.Connection != null && - command.Connection.State == ConnectionState.Open) + if (weOwnTheConnectionLifetime && command.Connection?.State == ConnectionState.Open) + { command.Connection.Close(); + DiagnosticSources.Retry.OwnedConnectionClosed(command, commandRetryPolicy, operationName); + } + throw; } }); @@ -81,9 +91,12 @@ public static async Task ExecuteReaderWithRetryAsync(this DbComman } catch (Exception) { - if (weOwnTheConnectionLifetime && command.Connection != null && - command.Connection.State == ConnectionState.Open) + if (weOwnTheConnectionLifetime && command.Connection?.State == ConnectionState.Open) + { await command.Connection.CloseAsync(); + DiagnosticSources.Retry.OwnedConnectionClosed(command, commandRetryPolicy, operationName); + } + throw; } }); @@ -103,7 +116,10 @@ public static object ExecuteScalarWithRetry(this DbCommand command, RetryPolicy finally { if (weOwnTheConnectionLifetime && command.Connection?.State == ConnectionState.Open) + { command.Connection.Close(); + DiagnosticSources.Retry.OwnedConnectionClosed(command, commandRetryPolicy, operationName); + } } }); } @@ -122,7 +138,10 @@ public static async Task ExecuteScalarWithRetryAsync(this DbCommand comm finally { if (weOwnTheConnectionLifetime && command.Connection?.State == ConnectionState.Open) + { await command.Connection.CloseAsync(); + DiagnosticSources.Retry.OwnedConnectionClosed(command, commandRetryPolicy, operationName); + } } }); } @@ -145,7 +164,9 @@ 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; } @@ -157,7 +178,9 @@ 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; } } 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 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) { } }