-
Notifications
You must be signed in to change notification settings - Fork 37
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
[Work in Progress] SqlException State 35 #619
Changes from all commits
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 |
---|---|---|
|
@@ -9,8 +9,10 @@ | |
using System.Threading.Tasks; | ||
using EnsureThat; | ||
using Microsoft.Data.SqlClient; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Health.SqlServer.Configs; | ||
using Microsoft.Health.SqlServer.Features.Storage; | ||
using Polly; | ||
|
||
namespace Microsoft.Health.SqlServer.Features.Client; | ||
|
||
|
@@ -21,25 +23,32 @@ public class SqlConnectionWrapper : IDisposable | |
private readonly ISqlConnectionBuilder _sqlConnectionBuilder; | ||
private readonly SqlRetryLogicBaseProvider _sqlRetryLogicBaseProvider; | ||
private readonly SqlServerDataStoreConfiguration _sqlServerDataStoreConfiguration; | ||
private readonly ILogger<SqlConnectionWrapper> _logger; | ||
|
||
private const int RetryAttempts = 3; | ||
private static readonly TimeSpan RetrySleepDuration = TimeSpan.FromSeconds(2); | ||
|
||
internal SqlConnectionWrapper( | ||
SqlTransactionHandler sqlTransactionHandler, | ||
ISqlConnectionBuilder connectionBuilder, | ||
SqlRetryLogicBaseProvider sqlRetryLogicBaseProvider, | ||
bool enlistInTransactionIfPresent, | ||
SqlServerDataStoreConfiguration sqlServerDataStoreConfiguration) | ||
SqlServerDataStoreConfiguration sqlServerDataStoreConfiguration, | ||
ILogger<SqlConnectionWrapper> logger) | ||
{ | ||
EnsureArg.IsNotNull(sqlTransactionHandler, nameof(sqlTransactionHandler)); | ||
EnsureArg.IsNotNull(connectionBuilder, nameof(connectionBuilder)); | ||
EnsureArg.IsNotNull(sqlRetryLogicBaseProvider, nameof(sqlRetryLogicBaseProvider)); | ||
EnsureArg.IsNotNull(sqlServerDataStoreConfiguration, nameof(sqlServerDataStoreConfiguration)); | ||
EnsureArg.IsNotNull(logger, nameof(logger)); | ||
|
||
_sqlServerDataStoreConfiguration = EnsureArg.IsNotNull(sqlServerDataStoreConfiguration, nameof(sqlServerDataStoreConfiguration)); | ||
|
||
_sqlTransactionHandler = sqlTransactionHandler; | ||
_enlistInTransactionIfPresent = enlistInTransactionIfPresent; | ||
_sqlConnectionBuilder = connectionBuilder; | ||
_sqlRetryLogicBaseProvider = sqlRetryLogicBaseProvider; | ||
_logger = logger; | ||
} | ||
|
||
public SqlConnection SqlConnection { get; private set; } | ||
|
@@ -64,7 +73,7 @@ internal async Task InitializeAsync(string initialCatalog = null, CancellationTo | |
|
||
if (SqlConnection.State != ConnectionState.Open) | ||
{ | ||
await SqlConnection.OpenAsync(cancellationToken).ConfigureAwait(false); | ||
await OpenRetriableSqlConnectionAsync(cancellationToken).ConfigureAwait(false); | ||
} | ||
|
||
if (_enlistInTransactionIfPresent && _sqlTransactionHandler.SqlTransactionScope != null) | ||
|
@@ -78,6 +87,34 @@ internal async Task InitializeAsync(string initialCatalog = null, CancellationTo | |
} | ||
} | ||
|
||
/// <summary> | ||
/// Open sql connection which is retried up to 3 times if <see cref="SqlException"/> is caught with state 35. | ||
/// <remarks> | ||
/// Retries have been added to cater to the following exception: | ||
/// "A connection was successfully established with the server, but then an error occurred during the login process. | ||
/// (provider: TCP Provider, error: 35 - An internal exception was caught)" | ||
/// </remarks> | ||
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. Do we want to add a test to exercise this logic specifically? This would retry on any internal exception - is this the only one that exists or is there potentially others with state 35? |
||
/// </summary> | ||
/// <param name="cancellationToken">Cancellation Token.</param> | ||
private async Task OpenRetriableSqlConnectionAsync(CancellationToken cancellationToken) | ||
{ | ||
int attempts = 1; | ||
|
||
await Policy.Handle<SqlException>(se => se.State == 35) | ||
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. nit: no need to await this. Simply return the |
||
.WaitAndRetryAsync( | ||
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. nit: indent by 1 here and below |
||
retryCount: RetryAttempts, | ||
sleepDurationProvider: (retryCount) => RetrySleepDuration, | ||
onRetry: (exception, retryCount) => | ||
{ | ||
_logger.LogWarning( | ||
exception, | ||
"Attempt {Attempt} of {MaxAttempts} to open Sql connection.", | ||
attempts++, | ||
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. Doesn't the |
||
RetryAttempts); | ||
}) | ||
.ExecuteAsync(token => SqlConnection.OpenAsync(token), cancellationToken).ConfigureAwait(false); | ||
} | ||
|
||
[Obsolete("Please use " + nameof(CreateRetrySqlCommand) + " or " + nameof(CreateNonRetrySqlCommand) + " instead.")] | ||
public SqlCommandWrapper CreateSqlCommand() | ||
{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we sure this is something we want to retry? The SQL Client library already retries a number of different errors? Do we know why this isn't retried? Is this a "retryable" exception? For example, I see an old issue in the issues page for this problem when moving from .NET 5 -> 6: dotnet/SqlClient#1402. Unsure if it's related, but I am reluctant to add new retries if we aren't quite sure why they're happening.