Skip to content
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

Retry http 429 responses #4897

Merged
merged 3 commits into from
Nov 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion src/NuGet.Core/NuGet.Protocol/EnhancedHttpRetryHelper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

#nullable enable

using System;
using NuGet.Common;

Expand All @@ -26,6 +28,11 @@ internal class EnhancedHttpRetryHelper
/// </summary>
public const int DefaultRetryCount = 6;

/// <summary>
/// The default value indicating whether or not to retry HTTP 429 responses.
/// </summary>
public const bool DefaultRetry429 = true;

/// <summary>
/// The environment variable used to change the delay value.
/// </summary>
Expand All @@ -41,6 +48,11 @@ internal class EnhancedHttpRetryHelper
/// </summary>
public const string RetryCountEnvironmentVariableName = "NUGET_ENHANCED_MAX_NETWORK_TRY_COUNT";

/// <summary>
/// The environment variabled to to disable retrying HTTP 429 responses.
/// </summary>
public const string Retry429EnvironmentVariableName = "NUGET_RETRY_HTTP_429";

private readonly IEnvironmentVariableReader _environmentVariableReader;

private bool? _isEnabled = null;
Expand All @@ -49,6 +61,8 @@ internal class EnhancedHttpRetryHelper

private int? _delayInMilliseconds = null;

private bool? _retry429 = null;

/// <summary>
/// Initializes a new instance of the <see cref="EnhancedHttpRetryHelper" /> class.
/// </summary>
Expand All @@ -74,6 +88,11 @@ public EnhancedHttpRetryHelper(IEnvironmentVariableReader environmentVariableRea
/// </summary>
internal int DelayInMilliseconds => _delayInMilliseconds ??= GetIntFromEnvironmentVariable(DelayInMillisecondsEnvironmentVariableName, defaultValue: DefaultDelayMilliseconds, _environmentVariableReader);

/// <summary>
/// Gets a value indicating whether or not retryable HTTP 4xx responses should be retried.
/// </summary>
internal bool Retry429 => _retry429 ??= GetBoolFromEnvironmentVariable(Retry429EnvironmentVariableName, defaultValue: true, _environmentVariableReader);

/// <summary>
/// Gets a <see cref="bool" /> value from the specified environment variable.
/// </summary>
Expand Down Expand Up @@ -106,7 +125,7 @@ private static int GetIntFromEnvironmentVariable(string variableName, int defaul
{
try
{
if (int.TryParse(environmentVariableReader.GetEnvironmentVariable(variableName), out int parsedValue))
if (int.TryParse(environmentVariableReader.GetEnvironmentVariable(variableName), out int parsedValue) && parsedValue >= 0)
{
return parsedValue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,11 @@ public async Task<HttpResponseMessage> SendAsync(
requestUri,
bodyStopwatch.ElapsedMilliseconds));

if ((int)response.StatusCode >= 500)
int statusCode = (int)response.StatusCode;
// 5xx == server side failure
// 408 == request timeout
// 429 == too many requests
if (statusCode >= 500 || ((statusCode == 408 || statusCode == 429) && _enhancedHttpRetryHelper.Retry429))
{
success = false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

#nullable enable

using System.Collections.Generic;
using Test.Utility;
using Xunit;

namespace NuGet.Protocol.Tests
{
public class EnhancedHttpRetryHelperTests
{
[Fact]
public void NoEnvionrmentVaraiblesSet_UsesDefaultValues()
{
// Arrange
TestEnvironmentVariableReader testEnvironmentVariableReader = new TestEnvironmentVariableReader(new Dictionary<string, string>());

// Act
EnhancedHttpRetryHelper helper = new(testEnvironmentVariableReader);

// Assert
Assert.Equal(helper.IsEnabled, EnhancedHttpRetryHelper.DefaultEnabled);
Assert.Equal(helper.RetryCount, EnhancedHttpRetryHelper.DefaultRetryCount);
Assert.Equal(helper.DelayInMilliseconds, EnhancedHttpRetryHelper.DefaultDelayMilliseconds);
Assert.Equal(helper.Retry429, EnhancedHttpRetryHelper.DefaultRetry429);
}

[Theory]
[InlineData("")]
[InlineData("5")]
[InlineData("something")]
public void InvalidBoolValue_UsesDefault(string value)
{
// Arrange
var dict = new Dictionary<string, string>()
{
[EnhancedHttpRetryHelper.IsEnabledEnvironmentVariableName] = value
};
var environmentReader = new TestEnvironmentVariableReader(dict);

// Act
EnhancedHttpRetryHelper helper = new(environmentReader);

// Assert
Assert.Equal(helper.IsEnabled, EnhancedHttpRetryHelper.DefaultEnabled);
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public void ValidBoolValue_UsesValue(bool value)
{
// Arrange
var dict = new Dictionary<string, string>()
{
[EnhancedHttpRetryHelper.IsEnabledEnvironmentVariableName] = value.ToString().ToLowerInvariant()
};
var environmentReader = new TestEnvironmentVariableReader(dict);

// Act
EnhancedHttpRetryHelper helper = new(environmentReader);

// Assert
Assert.Equal(helper.IsEnabled, value);
}

[Theory]
[InlineData("")]
[InlineData("true")]
[InlineData("something")]
[InlineData("-5")]
public void InvalidIntValue_UsesDefault(string value)
{
// Arrange
var dict = new Dictionary<string, string>()
{
[EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = value
};
var environmentReader = new TestEnvironmentVariableReader(dict);

// Act
EnhancedHttpRetryHelper helper = new(environmentReader);

// Assert
Assert.Equal(helper.RetryCount, EnhancedHttpRetryHelper.DefaultRetryCount);
}

[Theory]
[InlineData(5)]
[InlineData(10)]
[InlineData(100)]
public void ValidIntValue_UsesValue(int value)
{
// Arrange
var dict = new Dictionary<string, string>()
{
[EnhancedHttpRetryHelper.RetryCountEnvironmentVariableName] = value.ToString().ToLowerInvariant()
};
var environmentReader = new TestEnvironmentVariableReader(dict);

// Act
EnhancedHttpRetryHelper helper = new(environmentReader);

// Assert
Assert.Equal(helper.RetryCount, value);
}

}
}
Loading