using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using InfluxDB.Client.Core.Exceptions;
namespace InfluxDB.Client.Internal
{
///
/// RetryConfiguration.
///
internal class RetryAttempt
{
private static readonly ReadOnlyCollection RetryableStatuses =
new ReadOnlyCollection(
new[]
{
WebExceptionStatus.ConnectFailure,
WebExceptionStatus.NameResolutionFailure,
WebExceptionStatus.ProxyNameResolutionFailure,
WebExceptionStatus.SendFailure,
WebExceptionStatus.PipelineFailure,
WebExceptionStatus.ConnectionClosed,
WebExceptionStatus.KeepAliveFailure,
WebExceptionStatus.UnknownError,
WebExceptionStatus.ReceiveFailure,
WebExceptionStatus.RequestCanceled,
WebExceptionStatus.Timeout
});
private static readonly ReadOnlyCollection RetryableSocketErrors =
new ReadOnlyCollection(
new[]
{
SocketError.Interrupted,
SocketError.AccessDenied,
SocketError.TryAgain,
SocketError.TimedOut,
SocketError.NetworkDown,
SocketError.NetworkUnreachable,
SocketError.NetworkReset,
SocketError.ConnectionAborted,
SocketError.ConnectionReset,
SocketError.ConnectionRefused,
SocketError.HostDown,
SocketError.HostUnreachable,
SocketError.HostNotFound,
SocketError.AddressNotAvailable
});
internal Exception Error { get; }
private readonly int _count;
private readonly WriteOptions _writeOptions;
private readonly Random _random = new Random();
internal RetryAttempt(Exception error, int count, WriteOptions writeOptions)
{
Error = error;
_count = count;
_writeOptions = writeOptions;
}
///
/// Is this request retryable?
///
/// true if its retryable otherwise false
internal bool IsRetry()
{
//
// Max retries exceeded.
//
if (_count > _writeOptions.MaxRetries)
{
Trace.TraceWarning($"Max write retries exceeded. Response: '{Error.Message}'.");
return false;
}
if (Error is HttpException httpException && httpException.Status > 0)
{
//
// Retry HTTP error codes >= 429
//
return httpException.Status >= 429;
}
var networkException = GetWebException(Error);
if (networkException is WebException webException)
{
if (RetryableStatuses.Contains(webException.Status))
{
return true;
}
}
else if (networkException is SocketException socketException)
{
if (RetryableSocketErrors.Contains(socketException.SocketErrorCode))
{
return true;
}
}
return false;
}
///
/// Get current retry interval.
///
/// retry interval to sleep
internal long GetRetryInterval()
{
// from header
if (Error is HttpException httpException && httpException.RetryAfter.HasValue)
{
return httpException.RetryAfter.Value * 1000;
}
// from configuration
var rangeStart = _writeOptions.RetryInterval;
var rangeStop = _writeOptions.RetryInterval * _writeOptions.ExponentialBase;
var i = 1;
while (i < _count)
{
i++;
rangeStart = rangeStop;
rangeStop = rangeStop * _writeOptions.ExponentialBase;
if (rangeStop > _writeOptions.MaxRetryDelay)
{
break;
}
}
if (rangeStop > _writeOptions.MaxRetryDelay)
{
rangeStop = _writeOptions.MaxRetryDelay;
}
var retryInterval = (long)(rangeStart + (rangeStop - rangeStart) * _random.NextDouble());
Trace.WriteLine("The InfluxDB does not specify \"Retry-After\". " +
$"Use the default retryInterval: {retryInterval}");
return retryInterval;
}
internal static int JitterDelay(WriteOptions writeOptions)
{
return (int)(new Random().NextDouble() * writeOptions.JitterInterval);
}
private Exception GetWebException(Exception exception)
{
return exception switch
{
null => null,
WebException webException => webException,
SocketException webException => webException,
_ => GetWebException(exception.InnerException)
};
}
}
}