How do I get the current attempt number on a background job in Hangfire?
Asked Answered
E

3

18

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.

Experimental answered 14/7, 2016 at 7:33 Comment(0)
S
40

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
}
Swihart answered 9/10, 2017 at 23:6 Comment(3)
How do you pass PerformContext to the method though?Harp
@Alex Wiese github.com/HangfireIO/Hangfire/issues/…Swihart
As per @Swihart link: ... just pass it a null in your job definition, and it is automatically substituted with a correct instance later.Experimental
P
8

(NB! This is a solution to the OP's problem. It does not answer the question "How to get the current attempt number". If that is what you want, see the accepted answer for instance)

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://mcmap.net/q/650049/-how-do-i-get-the-current-attempt-number-on-a-background-job-in-hangfire

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...

Pursuit answered 9/5, 2018 at 12:50 Comment(1)
Worked like a charm for me. Very useful to be able to notify/log/do whatever after a job fails. Thank you!Pewter
A
6

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

Arrange answered 15/7, 2016 at 3:41 Comment(2)
Relying on text is hackish at best..., could break anytime... but this is better than nothing... thanks!.Voight
Use OnStateApplied instead of OnStateElection to avoid the problem with retries. See https://mcmap.net/q/650049/-how-do-i-get-the-current-attempt-number-on-a-background-job-in-hangfirePursuit

© 2022 - 2024 — McMap. All rights reserved.