-
Notifications
You must be signed in to change notification settings - Fork 293
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
WIP: Add retry logic to connection open and command execution #307
Changes from 12 commits
a0ba96e
ab9e93b
04b6988
1b05582
718dcdd
b46cc1b
1bed647
3a1620b
020519e
b06ee01
a1aa23e
58617ad
2f5aaf0
166316d
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,82 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System; | ||
|
||
namespace Microsoft.Data.SqlClient.Reliability | ||
{ | ||
|
||
/// <summary> | ||
/// A retry strategy with backoff parameters for calculating the exponential delay between retries. | ||
/// </summary> | ||
public class ExponentialBackoff : SqlRetryStrategy | ||
{ | ||
private readonly int retryCount; | ||
private readonly TimeSpan minBackoff; | ||
private readonly TimeSpan maxBackoff; | ||
private readonly TimeSpan deltaBackoff; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="ExponentialBackoff"/> class. | ||
/// </summary> | ||
public ExponentialBackoff() | ||
: this(DefaultClientRetryCount, DefaultMinBackoff, DefaultMaxBackoff, DefaultDeltaBackoff) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="ExponentialBackoff"/> class with the specified name and retry settings. | ||
/// </summary> | ||
/// <param name="retryCount">The maximum number of retry attempts.</param> | ||
/// <param name="minBackoff">The minimum backoff time</param> | ||
/// <param name="maxBackoff">The maximum backoff time.</param> | ||
/// <param name="deltaBackoff">The value that will be used to calculate a random delta in the exponential delay between retries.</param> | ||
public ExponentialBackoff(int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff) | ||
: this(retryCount, minBackoff, maxBackoff, deltaBackoff, DefaultFirstFastRetry) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="ExponentialBackoff"/> class with the specified name, retry settings, and fast retry option. | ||
/// </summary> | ||
/// <param name="retryCount">The maximum number of retry attempts.</param> | ||
/// <param name="minBackoff">The minimum backoff time</param> | ||
/// <param name="maxBackoff">The maximum backoff time.</param> | ||
/// <param name="deltaBackoff">The value that will be used to calculate a random delta in the exponential delay between retries.</param> | ||
/// <param name="firstFastRetry">true to immediately retry in the first attempt; otherwise, false. The subsequent retries will remain subject to the configured retry interval.</param> | ||
public ExponentialBackoff(int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff, bool firstFastRetry) | ||
: base(firstFastRetry) | ||
{ | ||
this.retryCount = retryCount; | ||
this.minBackoff = minBackoff; | ||
this.maxBackoff = maxBackoff; | ||
this.deltaBackoff = deltaBackoff; | ||
} | ||
|
||
/// <summary> | ||
/// Returns the corresponding ShouldRetry delegate. | ||
/// </summary> | ||
/// <returns>The ShouldRetry delegate.</returns> | ||
public override ShouldRetry GetShouldRetry() | ||
{ | ||
return delegate(int currentRetryCount, Exception lastException, out TimeSpan retryInterval) | ||
{ | ||
if (currentRetryCount < this.retryCount) | ||
{ | ||
var random = new Random(); | ||
|
||
var delta = (int)((Math.Pow(2.0, currentRetryCount) - 1.0) * random.Next((int)(this.deltaBackoff.TotalMilliseconds * 0.8), (int)(this.deltaBackoff.TotalMilliseconds * 1.2))); | ||
var interval = (int)Math.Min(checked(this.minBackoff.TotalMilliseconds + delta), this.maxBackoff.TotalMilliseconds); | ||
|
||
retryInterval = TimeSpan.FromMilliseconds(interval); | ||
|
||
return true; | ||
} | ||
|
||
retryInterval = TimeSpan.Zero; | ||
return false; | ||
}; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System; | ||
|
||
namespace Microsoft.Data.SqlClient.Reliability | ||
{ | ||
|
||
/// <summary> | ||
/// Represents a retry strategy with a specified number of retry attempts and a default, fixed time interval between retries. | ||
/// </summary> | ||
public class FixedInterval : SqlRetryStrategy | ||
{ | ||
private readonly int retryCount; | ||
private readonly TimeSpan retryInterval; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="FixedInterval"/> class. | ||
/// </summary> | ||
public FixedInterval() | ||
: this(DefaultClientRetryCount) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="FixedInterval"/> class with the specified number of retry attempts. | ||
/// </summary> | ||
/// <param name="retryCount">The number of retry attempts.</param> | ||
public FixedInterval(int retryCount) | ||
: this(retryCount, DefaultRetryInterval) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="FixedInterval"/> class with the specified number of retry attempts, time interval, and retry strategy. | ||
/// </summary> | ||
/// <param name="retryCount">The number of retry attempts.</param> | ||
/// <param name="retryInterval">The time interval between retries.</param> | ||
public FixedInterval(int retryCount, TimeSpan retryInterval) | ||
: this(retryCount, retryInterval, DefaultFirstFastRetry) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="FixedInterval"/> class with the specified number of retry attempts, time interval, retry strategy, and fast start option. | ||
/// </summary> | ||
/// <param name="retryCount">The number of retry attempts.</param> | ||
/// <param name="retryInterval">The time interval between retries.</param> | ||
/// <param name="firstFastRetry">true to immediately retry in the first attempt; otherwise, false. The subsequent retries will remain subject to the configured retry interval.</param> | ||
public FixedInterval(int retryCount, TimeSpan retryInterval, bool firstFastRetry) | ||
: base(firstFastRetry) | ||
{ | ||
this.retryCount = retryCount; | ||
this.retryInterval = retryInterval; | ||
} | ||
|
||
/// <summary> | ||
/// Returns the corresponding ShouldRetry delegate. | ||
/// </summary> | ||
/// <returns>The ShouldRetry delegate.</returns> | ||
public override ShouldRetry GetShouldRetry() | ||
{ | ||
if (this.retryCount == 0) | ||
{ | ||
return delegate(int currentRetryCount, Exception lastException, out TimeSpan interval) | ||
{ | ||
interval = TimeSpan.Zero; | ||
return false; | ||
}; | ||
} | ||
|
||
return delegate(int currentRetryCount, Exception lastException, out TimeSpan interval) | ||
{ | ||
if (currentRetryCount < this.retryCount) | ||
{ | ||
interval = this.retryInterval; | ||
return true; | ||
} | ||
|
||
interval = TimeSpan.Zero; | ||
return false; | ||
}; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
|
||
namespace Microsoft.Data.SqlClient.Reliability | ||
{ | ||
/// <summary> | ||
/// Defines an interface that must be implemented by custom components responsible for detecting specific transient conditions. | ||
/// </summary> | ||
public interface ITransientErrorDetectionStrategy | ||
{ | ||
/// <summary> | ||
/// Determines whether the specified exception represents a transient failure that can be compensated by a retry. | ||
/// </summary> | ||
/// <param name="ex">The exception object to be verified.</param> | ||
/// <returns>true if the specified exception is considered as transient; otherwise, false.</returns> | ||
bool IsTransient(Exception ex); | ||
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 anticipate anything apart from SqlException to be retryable ? 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. As of now, we're retrying on selected SqlException and TimeoutException types. |
||
|
||
/// <summary> | ||
/// Determines whether the specified exception represents a transient failure that can be compensated by a retry. | ||
/// </summary> | ||
/// <returns>true if the specified exception is considered as transient; otherwise, false.</returns> | ||
List<int> RetriableErrors | ||
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.
|
||
{ | ||
get; | ||
} | ||
} | ||
} |
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.
These are so many connection string options. And they dont seem descriptive enough for anyone to look at the keyword and get an idea of where these might apply. Are these meant to be for connections only ? Should they be prefixed with Connect?
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.
I agree there are many, but effectively different retry strategies have different attributes. For example, for "FixedInterval" you only need a "RetryInterval" attribute, while for "Incremental" you need a "RetryIncrement" as well, and for "ExponentialBackoff" you need to specify min, max and delta. On purpose i was not prefixing them with Connect to avoid any confusion with existing Connection Resiliency parameters.