/* * dbMango * * Copyright 2025 Deutsche Bank AG * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using log4net; using System.Reflection; namespace Rms.Risk.Mango.Pivot.Core; /// /// Utility class used to run a method using a retry strategy /// IF the method throws, the utility waits for a period of time and reruns it again, for a specific max number of times /// internal static class RetryHelper { private static readonly ILog _log = LogManager.GetLogger(MethodBase.GetCurrentMethod()!.DeclaringType!); public static void DoWithRetries( Action doWhat, int retries, TimeSpan delay, Action? logFunc = null) { logFunc ??= DefaultLogFunc; Exception? firstException = null; for ( var i = 0; i < retries; i++ ) { try { doWhat(); return; } catch ( Exception e ) { logFunc(i, retries, e); firstException ??= e; } if ( i == retries - 1 ) break; // If this is the last iteration, don't wait Thread.Sleep( delay ); } throw new ApplicationException($"{firstException?.Message} (after {retries} retries)", firstException); } public static T DoWithRetries(Func doWhat, int retries, TimeSpan delay, Action? resetFunc = null, Action? logFunc = null) { logFunc ??= DefaultLogFunc; Exception? firstException = null; for (var i = 0; i < retries; i++) { try { return doWhat(); } catch (Exception e) { logFunc(i, retries, e); firstException ??= e; } resetFunc?.Invoke(); if ( i == retries - 1 ) break; // If this is the last iteration, don't wait Thread.Sleep(delay); } throw new ApplicationException($"{firstException?.Message} (after {retries} retries)", firstException); } public static async Task DoWithRetriesAsync( Func> doAsyncWhat, int retries, TimeSpan delay, Action? resetFunc = null, Action? logFunc = null, CancellationToken token = default) { logFunc ??= DefaultLogFunc; Exception? firstException = null; for (var i = 0; i < retries; i++) { try { return await doAsyncWhat(); } catch (Exception e) { logFunc(i, retries, e); firstException ??= e; } resetFunc?.Invoke(); await Task.Delay(delay, token); } if (firstException != null) throw firstException; throw new ApplicationException("Retries exhausted"); } public static async Task DoWithRetriesAsync( Func doAsyncWhat, int retries, TimeSpan delay, Action? resetFunc = null, Action? logFunc = null, string? exceptionSubstring = null, CancellationToken token = default) { logFunc ??= DefaultLogFunc; Exception? firstException = null; for (var i = 0; i < retries; i++) { try { await doAsyncWhat(); return; } catch (Exception e) { if ( exceptionSubstring != null && !e.Message.Contains(exceptionSubstring, StringComparison.OrdinalIgnoreCase) ) { _log.Warn($"Exception does not contain substring {exceptionSubstring} Not retrying. {e.Message}"); throw; } logFunc(i, retries, e); firstException ??= e; } resetFunc?.Invoke(); await Task.Delay(delay, token); } if (firstException != null) throw firstException; } /// /// This is the default logger callback that will log failed attempts, its recommended that the user supplies their own so /// that they can log more contextual information about the operation /// /// /// /// private static void DefaultLogFunc(int iteration, int maxRetries, Exception e) { if (iteration < maxRetries - 1) _log.Warn($"Call failed, retrying RetriesLeft={maxRetries - iteration - 1}", e); else _log.Error("Call failed, retries exhausted", e); } }