Skip to content

Instantly share code, notes, and snippets.

@dfch
Last active August 28, 2021 22:20
Show Gist options
  • Save dfch/ebdb945d9f561bd5b91e83b431bc5fe8 to your computer and use it in GitHub Desktop.
Save dfch/ebdb945d9f561bd5b91e83b431bc5fe8 to your computer and use it in GitHub Desktop.
Transient Error Handling with automatic Retries in C# - d-fens.net -
/**
* Copyright 2017 d-fens GmbH
*
* 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 System;
using System.Diagnostics;
using System.Diagnostics.Contracts;
namespace Net.Appclusive.Core.Domain.General
{
public class Retry
{
public static RetryStrategyExponential ExponentialStrategy = new RetryStrategyExponential();
public static RetryStrategyIncremental IncrementalStrategy = new RetryStrategyIncremental();
public static RetryStrategyFixed FixedStrategy = new RetryStrategyFixed();
public static TResult Invoke<TResult>
(
Func<RetryStrategyBase, TResult> func,
Action<RetryStrategyBase, Exception> exceptionFilter = null,
Func<RetryStrategyBase, TResult> defaultReturnValueFactory = null,
RetryStrategyExponential strategy = null
)
{
Contract.Requires(null != func);
if (null == strategy)
{
strategy = ExponentialStrategy;
}
var waitTime = strategy.MinWaitTimeIntervalMilliseconds;
var stopwatch = Stopwatch.StartNew();
for (;;)
{
try
{
return func.Invoke(strategy);
}
catch (Exception ex)
{
exceptionFilter?.Invoke(strategy, ex);
}
if (strategy.CurrentAttempt >= strategy.MaxAttempts)
{
break;
}
// check if maximum time has already elapsed
if (stopwatch.ElapsedMilliseconds >= strategy.MaxWaitTimeMilliseconds)
{
break;
}
// wait for next retry
System.Threading.Thread.Sleep(waitTime);
// get next wait time interval
waitTime = strategy.GetNextWaitTime(waitTime);
strategy.NextAttempt();
}
return null != defaultReturnValueFactory
? defaultReturnValueFactory.Invoke(strategy)
: default(TResult);
}
public static TResult Invoke<TResult>
(
Func<TResult> func,
Action<Exception> exceptionFilter = null,
Func<TResult> defaultReturnValueFactory = null,
RetryStrategyExponential strategy = null
)
{
Contract.Requires(null != func);
if (null == strategy)
{
strategy = ExponentialStrategy;
}
var waitTime = strategy.MinWaitTimeIntervalMilliseconds;
var stopwatch = Stopwatch.StartNew();
for (;;)
{
try
{
return func.Invoke();
}
catch (Exception ex)
{
exceptionFilter?.Invoke(ex);
}
if (strategy.CurrentAttempt >= strategy.MaxAttempts)
{
break;
}
// check if maximum time has already elapsed
if (stopwatch.ElapsedMilliseconds >= strategy.MaxWaitTimeMilliseconds)
{
break;
}
// wait for next retry
System.Threading.Thread.Sleep(waitTime);
// get next wait time interval
waitTime = strategy.GetNextWaitTime(waitTime);
strategy.NextAttempt();
}
return null != defaultReturnValueFactory
? defaultReturnValueFactory.Invoke()
: default(TResult);
}
}
}
/**
* Copyright 2017 d-fens GmbH
*
* 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.
*/
namespace Net.Appclusive.Core.Domain.General
{
public abstract class RetryStrategyBase
{
public int MaxAttempts = int.MaxValue;
public int CurrentAttempt = 1;
public int MaxWaitTimeMilliseconds = 300 * 1000;
public int MinWaitTimeIntervalMilliseconds = 200;
public int MaxWaitTimeIntervalMilliseconds = 20 * 1000;
public object StateInfo;
public int NextAttempt()
{
return ++CurrentAttempt;
}
public abstract int GetNextWaitTime(int currentWaitTime);
}
}
/**
* Copyright 2017 d-fens GmbH
*
* 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 System;
using System.Diagnostics.Contracts;
namespace Net.Appclusive.Core.Domain.General
{
public class RetryStrategyCustom : RetryStrategyBase
{
private readonly Func<RetryStrategyCustom, int> waitTimeFactory;
protected RetryStrategyCustom(Func<RetryStrategyCustom, int> waitTimeFactory)
{
Contract.Requires(null != waitTimeFactory);
this.waitTimeFactory = waitTimeFactory;
}
public override int GetNextWaitTime(int currentWaitTime)
{
return waitTimeFactory(this);
}
}
}
/**
* Copyright 2017 d-fens GmbH
*
* 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 System;
namespace Net.Appclusive.Core.Domain.General
{
public class RetryStrategyExponential : RetryStrategyBase
{
public double Factor = Math.Sqrt(2);
public override int GetNextWaitTime(int currentWaitTime)
{
return (int) Math.Min(MaxWaitTimeIntervalMilliseconds, Math.Max(MinWaitTimeIntervalMilliseconds, currentWaitTime * Factor));
}
}
}
/**
* Copyright 2017 d-fens GmbH
*
* 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.
*/
namespace Net.Appclusive.Core.Domain.General
{
public class RetryStrategyFixed : RetryStrategyBase
{
public override int GetNextWaitTime(int currentWaitTime)
{
return currentWaitTime;
}
}
}
/**
* Copyright 2017 d-fens GmbH
*
* 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 System;
namespace Net.Appclusive.Core.Domain.General
{
public class RetryStrategyIncremental : RetryStrategyBase
{
public override int GetNextWaitTime(int currentWaitTime)
{
return Math.Min(MaxWaitTimeIntervalMilliseconds, Math.Max(MinWaitTimeIntervalMilliseconds, currentWaitTime));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment