diff --git a/src/MongoDB.Driver/Core/Operations/RetryabilityHelper.cs b/src/MongoDB.Driver/Core/Operations/RetryabilityHelper.cs index 0ccde3f9c7d..8a749925e85 100644 --- a/src/MongoDB.Driver/Core/Operations/RetryabilityHelper.cs +++ b/src/MongoDB.Driver/Core/Operations/RetryabilityHelper.cs @@ -108,6 +108,21 @@ public static void AddRetryableWriteErrorLabelIfRequired(MongoException exceptio } } + public static int GetRetryDelayMs(int attempt, double backoffBase = 2, int backoffInitial = 100, int backoffMax = 10_000) + { + Ensure.IsGreaterThanZero(attempt, nameof(attempt)); + Ensure.IsGreaterThanZero(backoffBase, nameof(backoffBase)); + Ensure.IsGreaterThanZero(backoffInitial, nameof(backoffInitial)); + Ensure.IsGreaterThan(backoffMax, backoffInitial, nameof(backoffMax)); + +#if NET6_0_OR_GREATER + var j = Random.Shared.NextDouble(); +#else + var j = (new Random()).NextDouble(); +#endif + return (int)(j * Math.Min(backoffMax, backoffInitial * Math.Pow(backoffBase, attempt - 1))); + } + public static bool IsCommandRetryable(BsonDocument command) { return diff --git a/tests/MongoDB.Driver.Tests/Core/Operations/RetryabilityHelperTests.cs b/tests/MongoDB.Driver.Tests/Core/Operations/RetryabilityHelperTests.cs index 9fc9db65be4..05a36b774b5 100644 --- a/tests/MongoDB.Driver.Tests/Core/Operations/RetryabilityHelperTests.cs +++ b/tests/MongoDB.Driver.Tests/Core/Operations/RetryabilityHelperTests.cs @@ -21,7 +21,6 @@ using MongoDB.Driver.Core.Misc; using MongoDB.Driver.Core.TestHelpers; using Xunit; -using MongoDB.Driver.Core.Connections; namespace MongoDB.Driver.Core.Operations { @@ -102,6 +101,42 @@ public void AddRetryableWriteErrorLabelIfRequired_should_add_RetryableWriteError hasRetryableWriteErrorLabel.Should().Be(shouldAddErrorLabel); } + [Theory] + [InlineData(1, 2, 100, 10000, 0, 100)] + [InlineData(2, 2, 100, 10000, 0, 200)] + [InlineData(3, 2, 100, 10000, 0, 400)] + [InlineData(9999, 2, 100, 10000, 0, 10000)] + + [InlineData(1, 1.5, 100, 10000, 0, 100)] + [InlineData(2, 1.5, 100, 10000, 0, 150)] + [InlineData(3, 1.5, 100, 10000, 0, 225)] + [InlineData(9999, 1.5, 100, 10000, 0, 10000)] + public void GetRetryDelayMs_should_return_expected_results(int attempt, double backoffBase, int backoffInitial, int backoffMax, int expectedRangeMin, int expectedRangeMax) + { + var result = RetryabilityHelper.GetRetryDelayMs(attempt, backoffBase, backoffInitial, backoffMax); + + result.Should().BeInRange(expectedRangeMin, expectedRangeMax); + } + + [Theory] + [InlineData(-1, 2, 100, 1000, "attempt")] + [InlineData(0, 2, 100, 1000, "attempt")] + [InlineData(1, -1, 100, 1000, "backoffBase")] + [InlineData(1, 0, 100, 1000, "backoffBase")] + [InlineData(1, 2, -1, 1000, "backoffInitial")] + [InlineData(1, 2, 0, 1000, "backoffInitial")] + [InlineData(1, 2, 100, -1, "backoffMax")] + [InlineData(1, 2, 100, 0, "backoffMax")] + [InlineData(1, 2, 100, 50, "backoffMax")] + + public void GetRetryDelayMs_throws_on_wrong_parameters(int attempt, double backoffBase, int backoffInitial, int backoffMax, string expectedParameterName) + { + var exception = Record.Exception(() => RetryabilityHelper.GetRetryDelayMs(attempt, backoffBase, backoffInitial, backoffMax)); + + exception.Should().BeOfType().Subject + .ParamName.Should().Be(expectedParameterName); + } + [Theory] [InlineData("{ txnNumber : 1 }", true)] [InlineData("{ commitTransaction : 1 }", true)]