我正在使用 hangfire 进行作业调度和启用 10 次的自动重试作业。此作业处理某些文件夹位置中的文件。在某些情况下,共享位置中不会有文件。我想要的是,如果 filenofoundexception。我不想在 hangfire 中重试这项工作,而这项工作应该会失败。但其他异常作业应该重试 10 次。
我个人会创建自己的方法属性来解决这个问题。
我已经为你写了一些伪代码,基于github上的原始hangfireattribute。
实现示例:
[FileNotFoundExceptionNoRetry(Attempts = 10)]//Retry 10 times
public void ProcessFilesJob()
{
try
{
//Process files in shared location...
}
catch (Exception)
{
throw;
}
}
如果抛出 FileNotFoundException 异常,以下属性代码理论上应该使作业处于“失败”状态,并继续重试任何其他异常。
我的更改是在 OnStateElection 方法下进行的。 请记住这是未经测试的代码。
public sealed class FileNotFoundExceptionNoRetryAttribute : JobFilterAttribute, IElectStateFilter, IApplyStateFilter
{
private static readonly ILog Logger = LogProvider.GetCurrentClassLogger();
private const int DefaultRetryAttempts = 10;
private int _attempts;
public FileNotFoundExceptionNoRetryAttribute()
{
Attempts = DefaultRetryAttempts;
LogEvents = true;
OnAttemptsExceeded = AttemptsExceededAction.Fail;
}
public int Attempts
{
get { return _attempts; }
set
{
if (value < 0)
{
throw new ArgumentOutOfRangeException("value", "Attempts value must be equal or greater than zero.");
}
_attempts = value;
}
}
public AttemptsExceededAction OnAttemptsExceeded { get; set; }
public bool LogEvents { get; set; }
public void OnStateElection(ElectStateContext context)
{
var failedState = context.CandidateState as FailedState;
if (failedState != null && failedState.Exception != null && failedState.Exception is FileNotFoundException)
{//FileNotFoundException was thrown dont retry.
Attempts = 0;
OnAttemptsExceeded = AttemptsExceededAction.Fail;
}
if (failedState == null)
{
// This filter accepts only failed job state.
return;
}
var retryAttempt = context.GetJobParameter<int>("RetryCount") + 1;
if (retryAttempt <= Attempts)
{
ScheduleAgainLater(context, retryAttempt, failedState);
}
else if (retryAttempt > Attempts && OnAttemptsExceeded == AttemptsExceededAction.Delete)
{
TransitionToDeleted(context, failedState);
}
else
{
if (LogEvents)
{
Logger.ErrorException(
String.Format(
"Failed to process the job '{0}': an exception occurred.",
context.JobId),
failedState.Exception);
}
}
}
/// <summary>
/// Schedules the job to run again later. See <see cref="SecondsToDelay"/>.
/// </summary>
/// <param name="context">The state context.</param>
/// <param name="retryAttempt">The count of retry attempts made so far.</param>
/// <param name="failedState">Object which contains details about the current failed state.</param>
private void ScheduleAgainLater(ElectStateContext context, int retryAttempt, FailedState failedState)
{
context.SetJobParameter("RetryCount", retryAttempt);
var delay = TimeSpan.FromSeconds(SecondsToDelay(retryAttempt));
const int maxMessageLength = 50;
var exceptionMessage = failedState.Exception.Message;
// If attempt number is less than max attempts, we should
// schedule the job to run again later.
context.CandidateState = new ScheduledState(delay)
{
Reason = String.Format(
"Retry attempt {0} of {1}: {2}",
retryAttempt,
Attempts,
exceptionMessage.Length > maxMessageLength
? exceptionMessage.Substring(0, maxMessageLength - 1) + "…"
: exceptionMessage)
};
if (LogEvents)
{
Logger.WarnException(
String.Format(
"Failed to process the job '{0}': an exception occurred. Retry attempt {1} of {2} will be performed in {3}.",
context.JobId,
retryAttempt,
Attempts,
delay),
failedState.Exception);
}
}
/// <summary>
/// Transition the candidate state to the deleted state.
/// </summary>
/// <param name="context">The state context.</param>
/// <param name="failedState">Object which contains details about the current failed state.</param>
private void TransitionToDeleted(ElectStateContext context, FailedState failedState)
{
context.CandidateState = new DeletedState
{
Reason = String.Format("Automatic deletion after retry count exceeded {0}", Attempts)
};
if (LogEvents)
{
Logger.WarnException(
String.Format(
"Failed to process the job '{0}': an exception occured. Job was automatically deleted because the retry attempt count exceeded {1}.",
context.JobId,
Attempts),
failedState.Exception);
}
}
// delayed_job uses the same basic formula
private static int SecondsToDelay(long retryCount)
{
var random = new Random();
return (int)Math.Round(
Math.Pow(retryCount - 1, 4) + 15 + (random.Next(30) * (retryCount)));
}
public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
if (context.NewState is ScheduledState &&
context.NewState.Reason != null &&
context.NewState.Reason.StartsWith("Retry attempt"))
{
transaction.AddToSet("retries", context.JobId);
}
}
public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
if (context.OldStateName == ScheduledState.StateName)
{
transaction.RemoveFromSet("retries", context.JobId);
}
}
}
我认为这个解决方案更简单:
public class DummyJobsClass{
// A RetryCount number that your system should never allow!
public const int ExhaustRetryCounts = 9999;
[AutomaticRetry(Attempts = 10)]
public void MyDummyJobMethod(Guid someArgument, string anotherOne, PerformContext performContext){
// ...
if(true == SomeConditionThatJobMustNotRetry){
// Set the job RetryCount to a Number that is 'Never Allowed' in your system...
performContext.SetJobParameter("RetryCount", ExhaustRetryCounts);
// Throw some exception to exit the Job and have a nice reason on your Dashboard...
throw new Exception("Houston, we have found an Alien! Never get back here!");
}
// ...
}
}
来源:https://discuss.hangfire.io/t/fail-a-job-with-conditional-retry/5193/2