How do I get the current attempt number on a backg

2019-04-28 22:29发布

There are some database operations I need to execute before the end of the final attempt of my Hangfire background job (I need to delete the database record related to the job)

My current job is set with the following attribute:
[AutomaticRetry(Attempts = 5, OnAttemptsExceeded = AttemptsExceededAction.Delete)]

With that in mind, I need to determine what the current attempt number is, but am struggling to find any documentation in that regard from a Google search or Hangfire.io documentation.

3条回答
迷人小祖宗
2楼-- · 2019-04-28 22:46

Simply add PerformContext to your job method; you'll also be able to access your JobId from this object. For attempt number, this still relies on magic strings, but it's a little less flaky than the current/only answer:

public void SendEmail(PerformContext context, string emailAddress)
{
    string jobId = context.BackgroundJob.Id;
    int retryCount = context.GetJobParameter<int>("RetryCount");
    // send an email
}
查看更多
看我几分像从前
3楼-- · 2019-04-28 22:50

You can use OnPerforming or OnPerformed method of IServerFilter if you want to check the attempts or if you want you can just wait on OnStateElection of IElectStateFilter. I don't know exactly what requirement you have so it's up to you. Here's the code you want :)

public class JobStateFilter : JobFilterAttribute, IElectStateFilter, IServerFilter
{
    public void OnStateElection(ElectStateContext context)
    {
        // all failed job after retry attempts comes here
        var failedState = context.CandidateState as FailedState;

        if (failedState == null) return;
    }

    public void OnPerforming(PerformingContext filterContext)
    {
        // do nothing
    }

    public void OnPerformed(PerformedContext filterContext)
    {
        // you have an option to move all code here on OnPerforming if you want.
        var api = JobStorage.Current.GetMonitoringApi();

        var job = api.JobDetails(filterContext.BackgroundJob.Id);

        foreach(var history in job.History)
        {
            // check reason property and you will find a string with
            // Retry attempt 3 of 3: The method or operation is not implemented.            
        }
    }   
}

How to add your filter

GlobalJobFilters.Filters.Add(new JobStateFilter());

----- or 

var options = new BackgroundJobServerOptions
{   
    FilterProvider = new JobFilterCollection { new JobStateFilter() };
};

app.UseHangfireServer(options, storage);

Sample output :

enter image description here

查看更多
Rolldiameter
4楼-- · 2019-04-28 23:02

(NB! This is a solution to the OP's problem. It does not answer the question "How to get the current attempt number")

Use a job filter and the OnStateApplied callback:

public class CleanupAfterFailureFilter : JobFilterAttribute, IServerFilter, IApplyStateFilter
{
    public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
    {
        try
        {
            var failedState = context.NewState as FailedState;
            if (failedState != null)
            {
                // Job has finally failed (retry attempts exceeded)
                // *** DO YOUR CLEANUP HERE ***
            }
        }
        catch (Exception)
        {
            // Unhandled exceptions can cause an endless loop.
            // Therefore, catch and ignore them all.
            // See notes below.
        }
    }

    public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
    {
        // Must be implemented, but can be empty.
    }
}

Add the filter directly to the job function:

[CleanupAfterFailureFilter]
public static void MyJob()

or add it globally:

GlobalJobFilters.Filters.Add(new CleanupAfterFailureFilter ());

or like this:

var options = new BackgroundJobServerOptions
{   
    FilterProvider = new JobFilterCollection { new CleanupAfterFailureFilter () };
};

app.UseHangfireServer(options, storage);

Or see http://docs.hangfire.io/en/latest/extensibility/using-job-filters.html for more information about job filters.

NOTE: This is based on the accepted answer: https://stackoverflow.com/a/38387512/2279059

The difference is that OnStateApplied is used instead of OnStateElection, so the filter callback is invoked only after the maximum number of retries. A downside to this method is that the state transition to "failed" cannot be interrupted, but this is not needed in this case and in most scenarios where you just want to do some cleanup after a job has failed.

NOTE: Empty catch handlers are bad, because they can hide bugs and make them hard to debug in production. It is necessary here, so the callback doesn't get called repeatedly forever. You may want to log exceptions for debugging purposes. It is also advisable to reduce the risk of exceptions in a job filter. One possibility is, instead of doing the cleanup work in-place, to schedule a new background job which runs if the original job failed. Be careful to not apply the filter CleanupAfterFailureFilter to it, though. Don't register it globally, or add some extra logic to it...

查看更多
登录 后发表回答