2024-05-09 07:32:48 +00:00
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
|
|
|
|
|
namespace Utils
|
2023-04-12 11:53:55 +00:00
|
|
|
|
{
|
|
|
|
|
public static class Time
|
|
|
|
|
{
|
|
|
|
|
public static void Sleep(TimeSpan span)
|
|
|
|
|
{
|
|
|
|
|
Thread.Sleep(span);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static T Wait<T>(Task<T> task)
|
|
|
|
|
{
|
|
|
|
|
task.Wait();
|
|
|
|
|
return task.Result;
|
|
|
|
|
}
|
2023-04-14 12:53:39 +00:00
|
|
|
|
|
2023-12-20 14:56:03 +00:00
|
|
|
|
public static void Wait(Task task)
|
|
|
|
|
{
|
|
|
|
|
task.Wait();
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-23 09:15:14 +00:00
|
|
|
|
public static string FormatDuration(TimeSpan? d)
|
|
|
|
|
{
|
|
|
|
|
if (d == null) return "[NULL]";
|
|
|
|
|
return FormatDuration(d.Value);
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-14 12:53:39 +00:00
|
|
|
|
public static string FormatDuration(TimeSpan d)
|
|
|
|
|
{
|
|
|
|
|
var result = "";
|
|
|
|
|
if (d.Days > 0) result += $"{d.Days} days, ";
|
|
|
|
|
if (d.Hours > 0) result += $"{d.Hours} hours, ";
|
|
|
|
|
if (d.Minutes > 0) result += $"{d.Minutes} mins, ";
|
|
|
|
|
result += $"{d.Seconds} secs";
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2023-06-01 14:28:34 +00:00
|
|
|
|
|
2023-11-17 13:51:32 +00:00
|
|
|
|
public static TimeSpan ParseTimespan(string span)
|
|
|
|
|
{
|
|
|
|
|
span = span.Replace(" ", "").Replace(",", "");
|
|
|
|
|
var result = TimeSpan.Zero;
|
|
|
|
|
var number = "";
|
|
|
|
|
foreach (var c in span)
|
|
|
|
|
{
|
|
|
|
|
if (char.IsNumber(c)) number += c;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var value = Convert.ToInt32(number);
|
|
|
|
|
number = "";
|
|
|
|
|
|
|
|
|
|
if (c == 'd') result += TimeSpan.FromDays(value);
|
|
|
|
|
else if (c == 'h') result += TimeSpan.FromHours(value);
|
|
|
|
|
else if (c == 'm') result += TimeSpan.FromMinutes(value);
|
|
|
|
|
else if (c == 's') result += TimeSpan.FromSeconds(value);
|
|
|
|
|
else throw new Exception("Unknown time modifier: " + c);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(number))
|
|
|
|
|
{
|
|
|
|
|
var value = Convert.ToInt32(number);
|
|
|
|
|
result += TimeSpan.FromSeconds(value);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-14 07:17:25 +00:00
|
|
|
|
public static void WaitUntil(Func<bool> predicate, string msg)
|
2023-06-01 14:28:34 +00:00
|
|
|
|
{
|
2024-04-14 07:17:25 +00:00
|
|
|
|
WaitUntil(predicate, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(1), msg);
|
2023-06-01 14:28:34 +00:00
|
|
|
|
}
|
2023-04-17 14:28:07 +00:00
|
|
|
|
|
2024-04-14 07:17:25 +00:00
|
|
|
|
public static void WaitUntil(Func<bool> predicate, TimeSpan timeout, TimeSpan retryDelay, string msg)
|
2023-04-17 14:28:07 +00:00
|
|
|
|
{
|
|
|
|
|
var start = DateTime.UtcNow;
|
2024-04-14 07:17:25 +00:00
|
|
|
|
var tries = 1;
|
2023-04-17 14:28:07 +00:00
|
|
|
|
var state = predicate();
|
|
|
|
|
while (!state)
|
|
|
|
|
{
|
2024-04-14 07:17:25 +00:00
|
|
|
|
var duration = DateTime.UtcNow - start;
|
|
|
|
|
if (duration > timeout)
|
2023-04-17 14:28:07 +00:00
|
|
|
|
{
|
2024-04-14 07:17:25 +00:00
|
|
|
|
throw new TimeoutException($"Operation timed out after {tries} tries over (total) {FormatDuration(duration)}. '{msg}'");
|
2023-04-17 14:28:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-22 08:02:16 +00:00
|
|
|
|
Sleep(retryDelay);
|
2023-04-17 14:28:07 +00:00
|
|
|
|
state = predicate();
|
2024-04-14 07:17:25 +00:00
|
|
|
|
tries++;
|
2023-04-17 14:28:07 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-05-10 07:55:36 +00:00
|
|
|
|
|
2023-05-31 11:15:41 +00:00
|
|
|
|
public static void Retry(Action action, string description)
|
2023-05-10 07:55:36 +00:00
|
|
|
|
{
|
2024-05-02 06:41:20 +00:00
|
|
|
|
Retry(action, TimeSpan.FromSeconds(30), description);
|
2023-05-10 07:55:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-05-31 11:15:41 +00:00
|
|
|
|
public static T Retry<T>(Func<T> action, string description)
|
2023-05-10 07:55:36 +00:00
|
|
|
|
{
|
2024-05-02 06:41:20 +00:00
|
|
|
|
return Retry(action, TimeSpan.FromSeconds(30), description);
|
2023-05-10 07:55:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-02 06:41:20 +00:00
|
|
|
|
public static void Retry(Action action, TimeSpan maxTimeout, string description)
|
2023-05-10 07:55:36 +00:00
|
|
|
|
{
|
2024-05-02 06:41:20 +00:00
|
|
|
|
Retry(action, maxTimeout, TimeSpan.FromSeconds(5), description);
|
2023-05-10 07:55:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-02 06:41:20 +00:00
|
|
|
|
public static T Retry<T>(Func<T> action, TimeSpan maxTimeout, string description)
|
2023-05-10 07:55:36 +00:00
|
|
|
|
{
|
2024-05-02 06:41:20 +00:00
|
|
|
|
return Retry(action, maxTimeout, TimeSpan.FromSeconds(5), description);
|
2023-05-10 07:55:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-02 06:41:20 +00:00
|
|
|
|
public static void Retry(Action action, TimeSpan maxTimeout, TimeSpan retryTime, string description)
|
2023-05-10 07:55:36 +00:00
|
|
|
|
{
|
|
|
|
|
var start = DateTime.UtcNow;
|
2024-05-02 06:41:20 +00:00
|
|
|
|
var tries = 1;
|
2024-05-09 07:32:48 +00:00
|
|
|
|
var tryInfo = new List<(Exception, TimeSpan)>();
|
2024-05-02 06:41:20 +00:00
|
|
|
|
|
2023-05-10 07:55:36 +00:00
|
|
|
|
while (true)
|
|
|
|
|
{
|
2024-05-02 06:41:20 +00:00
|
|
|
|
var duration = DateTime.UtcNow - start;
|
|
|
|
|
if (duration > maxTimeout)
|
2023-05-10 07:55:36 +00:00
|
|
|
|
{
|
2024-05-09 07:32:48 +00:00
|
|
|
|
var info = FormatTryInfos(tryInfo);
|
|
|
|
|
throw new TimeoutException($"Retry '{description}' timed out after {tries} tries over {FormatDuration(duration)}.{Environment.NewLine}{info}");
|
2023-05-10 07:55:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-09 07:32:48 +00:00
|
|
|
|
var sw = Stopwatch.StartNew();
|
2023-05-10 07:55:36 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
action();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2024-05-09 07:32:48 +00:00
|
|
|
|
tryInfo.Add((ex, sw.Elapsed));
|
2024-05-02 06:41:20 +00:00
|
|
|
|
tries++;
|
2023-05-10 07:55:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Sleep(retryTime);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-09 07:32:48 +00:00
|
|
|
|
private static string FormatTryInfos(List<(Exception, TimeSpan)> tryInfo)
|
|
|
|
|
{
|
|
|
|
|
return string.Join(Environment.NewLine, tryInfo.Select(FormatTryInfo).ToArray());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string FormatTryInfo((Exception, TimeSpan) info, int index)
|
|
|
|
|
{
|
|
|
|
|
return $"Attempt {index} took {FormatDuration(info.Item2)} and failed with exception {info.Item1}.";
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-09 07:38:04 +00:00
|
|
|
|
private static Action<int> failedCallback = i => { };
|
|
|
|
|
public static void SetRetryFailedCallback(Action<int> onRetryFailed)
|
|
|
|
|
{
|
|
|
|
|
failedCallback = onRetryFailed;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-02 06:41:20 +00:00
|
|
|
|
public static T Retry<T>(Func<T> action, TimeSpan maxTimeout, TimeSpan retryTime, string description)
|
2023-05-10 07:55:36 +00:00
|
|
|
|
{
|
|
|
|
|
var start = DateTime.UtcNow;
|
2024-05-02 06:41:20 +00:00
|
|
|
|
var tries = 1;
|
2023-05-10 07:55:36 +00:00
|
|
|
|
var exceptions = new List<Exception>();
|
2024-05-02 06:41:20 +00:00
|
|
|
|
|
2023-05-10 07:55:36 +00:00
|
|
|
|
while (true)
|
|
|
|
|
{
|
2024-05-02 06:41:20 +00:00
|
|
|
|
var duration = DateTime.UtcNow - start;
|
|
|
|
|
if (duration > maxTimeout)
|
2023-05-10 07:55:36 +00:00
|
|
|
|
{
|
2024-05-02 06:41:20 +00:00
|
|
|
|
throw new TimeoutException($"Retry '{description}' timed out after {tries} tries over {FormatDuration(duration)}.", new AggregateException(exceptions));
|
2023-05-10 07:55:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return action();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
exceptions.Add(ex);
|
2024-05-09 07:38:04 +00:00
|
|
|
|
failedCallback(tries);
|
2024-05-02 06:41:20 +00:00
|
|
|
|
tries++;
|
2023-05-10 07:55:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Sleep(retryTime);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-04-12 11:53:55 +00:00
|
|
|
|
}
|
|
|
|
|
}
|