Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 655
Jimbo Avatar asked Jul 14 '16 07:07

Jimbo


People also ask

How do I stop Hangfire background?

There is no “Cancel” button for jobs in the built-in dashboard, only a “Delete” button. Using that Delete button will remove the Job from the running jobs in the built-in dashboard (near) instantly.

How do you trigger a Hangfire manually?

The idea is, on a loading page, there is an email input field. Once the user writes his email and clicks the Get Email button, a background job should trigger. It will check if the transaction is complete, and once it is, it will send an email to the user.

Is Hangfire Russian?

Starting from Mar 8, 2022 Hangfire is owned by Hangfire OÜ (private limited), an Estonian company.


2 Answers

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
}
like image 189
Olson.dev Avatar answered Oct 17 '22 07:10

Olson.dev


(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://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...

like image 7
Florian Winter Avatar answered Oct 17 '22 06:10

Florian Winter