-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Leverage "Retry-After" usage to LUIS API calls
If a "Retry-After" HTTP response header is returned by LUIS, this change ensures that header is respected. Fixes #333
- Loading branch information
Showing
10 changed files
with
254 additions
and
154 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
namespace NLU.DevOps.Luis | ||
{ | ||
/// <summary> | ||
/// Information about the batch test evaluation operation status. | ||
/// </summary> | ||
/// <typeparam name="T">Type of response value.</typeparam> | ||
public class OperationResponse<T> | ||
{ | ||
internal OperationResponse(T value, string retryAfter) | ||
{ | ||
this.Value = value; | ||
this.RetryAfter = retryAfter; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the response value. | ||
/// </summary> | ||
public T Value { get; } | ||
|
||
/// <summary> | ||
/// Gets the HTTP 'Retry-After' header. | ||
/// </summary> | ||
public string RetryAfter { get; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
namespace NLU.DevOps.Luis | ||
{ | ||
using System.Linq; | ||
using System.Net.Http; | ||
|
||
/// <summary> | ||
/// Factory methods for <see cref="OperationResponse{T}"/>. | ||
/// </summary> | ||
public static class OperationResponse | ||
{ | ||
/// <summary> | ||
/// Creates an instance of <see cref="OperationResponse{T}"/>. | ||
/// </summary> | ||
/// <typeparam name="T">Type of response value.</typeparam> | ||
/// <param name="value">Response value.</param> | ||
/// <param name="response">HTTP response.</param> | ||
/// <returns>Instance of <see cref="OperationResponse{T}"/>.</returns> | ||
public static OperationResponse<T> Create<T>(T value, HttpResponseMessage response = default) | ||
{ | ||
var retryAfter = response?.Headers?.GetValues(Retry.RetryAfterHeader).FirstOrDefault(); | ||
return new OperationResponse<T>(value, retryAfter); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
namespace NLU.DevOps.Luis | ||
{ | ||
using System; | ||
using System.Globalization; | ||
using System.Linq; | ||
using System.Net; | ||
using System.Text.RegularExpressions; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.Azure.CognitiveServices.Language.LUIS.Authoring.Models; | ||
#if LUIS_V2 | ||
using ErrorException = Microsoft.Azure.CognitiveServices.Language.LUIS.Runtime.Models.APIErrorException; | ||
#endif | ||
|
||
internal static class Retry | ||
{ | ||
public const string RetryAfterHeader = "Retry-After"; | ||
|
||
private static readonly Regex RetryAfterSecondsRegex = new Regex(@"^\d+$"); | ||
|
||
private static TimeSpan DefaultTransientDelay { get; } = TimeSpan.FromMilliseconds(100); | ||
|
||
public static TimeSpan GetRetryAfterDelay(string retryAfter, TimeSpan? defaultDelay = default) | ||
{ | ||
if (retryAfter == null) | ||
{ | ||
return defaultDelay ?? DefaultTransientDelay; | ||
} | ||
|
||
if (RetryAfterSecondsRegex.IsMatch(retryAfter)) | ||
{ | ||
return TimeSpan.FromSeconds(int.Parse(retryAfter, CultureInfo.InvariantCulture)); | ||
} | ||
|
||
return DateTimeOffset.Parse(retryAfter, CultureInfo.InvariantCulture) - DateTimeOffset.Now; | ||
} | ||
|
||
public static CancellationTokenHolder With(CancellationToken cancellationToken) | ||
{ | ||
return new CancellationTokenHolder(cancellationToken); | ||
} | ||
|
||
private static async Task<TResult> OnTransientExceptionAsync<TResult, TException>( | ||
Func<Task<TResult>> func, | ||
Func<TException, HttpStatusCode> statusCodeSelector, | ||
Func<TException, string> retryAfterDelaySelector = default, | ||
int retryCount = int.MaxValue, | ||
CancellationToken cancellationToken = default) | ||
where TException : Exception | ||
{ | ||
var count = 0; | ||
while (count++ < retryCount) | ||
{ | ||
cancellationToken.ThrowIfCancellationRequested(); | ||
|
||
try | ||
{ | ||
return await func().ConfigureAwait(false); | ||
} | ||
catch (TException ex) | ||
when (count < retryCount && IsTransientStatusCode(statusCodeSelector(ex))) | ||
{ | ||
var delay = GetRetryAfterDelay(retryAfterDelaySelector?.Invoke(ex)); | ||
await Task.Delay(delay, cancellationToken).ConfigureAwait(false); | ||
} | ||
} | ||
|
||
throw new InvalidOperationException("Exception will be rethrown before reaching this point."); | ||
} | ||
|
||
private static bool IsTransientStatusCode(HttpStatusCode statusCode) | ||
{ | ||
return statusCode == HttpStatusCode.TooManyRequests | ||
|| (statusCode >= HttpStatusCode.InternalServerError | ||
&& statusCode != HttpStatusCode.HttpVersionNotSupported | ||
&& statusCode != HttpStatusCode.NotImplemented); | ||
} | ||
|
||
public class CancellationTokenHolder | ||
{ | ||
public CancellationTokenHolder(CancellationToken cancellationToken) | ||
{ | ||
this.CancellationToken = cancellationToken; | ||
} | ||
|
||
private CancellationToken CancellationToken { get; } | ||
|
||
public Task<T> OnTransientErrorAsync<T>(Func<Task<T>> func) | ||
{ | ||
return OnTransientExceptionAsync( | ||
func, | ||
(ErrorException ex) => ex.Response.StatusCode, | ||
(ErrorException ex) => ex.Response.Headers?[RetryAfterHeader]?.FirstOrDefault(), | ||
cancellationToken: this.CancellationToken); | ||
} | ||
|
||
public Task<T> OnTransientErrorResponseAsync<T>(Func<Task<T>> func) | ||
{ | ||
return OnTransientExceptionAsync( | ||
func, | ||
(ErrorResponseException ex) => ex.Response.StatusCode, | ||
(ErrorResponseException ex) => ex.Response.Headers?[RetryAfterHeader]?.FirstOrDefault(), | ||
cancellationToken: this.CancellationToken); | ||
} | ||
|
||
public Task<T> OnTransientWebExceptionAsync<T>(Func<Task<T>> func) | ||
{ | ||
return OnTransientExceptionAsync( | ||
func, | ||
(WebException ex) => (ex.Response as HttpWebResponse)?.StatusCode ?? default, | ||
(WebException ex) => (ex.Response as HttpWebResponse)?.Headers?[RetryAfterHeader], | ||
cancellationToken: this.CancellationToken); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.