特定异常的 Hangfire 自动重试

问题描述 投票:0回答:2

我正在使用 hangfire 进行作业调度和启用 10 次的自动重试作业。此作业处理某些文件夹位置中的文件。在某些情况下,共享位置中不会有文件。我想要的是,如果 filenofoundexception。我不想在 hangfire 中重试这项工作,而这项工作应该会失败。但其他异常作业应该重试 10 次。

c#-4.0 hangfire
2个回答
3
投票

我个人会创建自己的方法属性来解决这个问题。

我已经为你写了一些代码,基于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);
        }
    }
}

0
投票

我认为这个解决方案更简单:

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

© www.soinside.com 2019 - 2024. All rights reserved.