Pause/disable recurring job

Very similar to github issue #173, it would be really handy to have a method of pausing (or disabling) a recurring job.

For example, say there is a recurring job which wakes up every x minutes to scan a file on a remote computer, and the user knows that that machine won’t be on-line for the rest of the day (for maintenance or whatever), so they set the job to be paused.

I know that I could just have the job fail, and leave it to retry, but that would be unhelpful in this scenario for several reasons: My user facing dashboard would report a problem with the remote computer, and after several failed retries emails would start getting sent which don’t need to be.

I also know I could probably ‘fake’ pausing the recurring job by removing it when the user clicked ‘pause’, and adding it back when the user ‘un-paused’ it. I’m happy to do this, but it just feels a little odd, I’d much rather be able to just pause the job through the Hangfire API.

+1. This feature is pretty simple to add. Scheduled to version 1.2.0 to not to bloat the next release.

3 Likes

Odinserj,
Do you have any idea when the “pause” functionality for recurring jobs might be released?
Short of that, can you give me some idea how I might code a work-around in the meantime?
Thanks for a great piece of software!

Are there any updates on when / if this will be added?

Is the best workaround to remove the job, and then re-add it?

You can simply use a condition inside a recurring job and store it in your database to prevent job running:

public void MyMethod()
{
    if (someCondition) { return; }

    /* ... */
}

Or use filters to extend Hangfire as shown below (I haven’t tested the code):

public class CanBePausedAttribute : JobFilterAttribute, IServerFilter
{
    public void OnPerforming(PerformingContext filterContext)
    {
        var values = filterContext.Connection.GetAllItemsFromSet("paused-jobs");
        if (values.Contains(filterContext.Job.Type.Name))
        {
            filterContext.Canceled = true;
        }
    }

    public void OnPerformed(PerformedContext filterContext)
    {
    }
}

public static class PauseJobStorageExtensions
{
    public static void Pause(this IStorageConnection connection, Type type)
    {
        if (connection == null) throw new ArgumentNullException("connection");
        if (type == null) throw new ArgumentNullException("type");

        using (var transaction = connection.CreateWriteTransaction())
        {
            transaction.AddToSet("paused-jobs", type.Name);
            transaction.Commit();
        }
    }

    public static void Resume(this IStorageConnection connection, Type type)
    {
        if (connection == null) throw new ArgumentNullException("connection");
        if (type == null) throw new ArgumentNullException("type");

        using (var transaction = connection.CreateWriteTransaction())
        {
            transaction.RemoveFromSet("paused-jobs", type.Name);
            transaction.Commit();
        }
    }
}

class Program
{
    [CanBePaused]
    static void Main()
    {
        var storage = new SqlServerStorage("connection_string");
        var client = new BackgroundJobClient(storage);

        client.Enqueue(() => Program.Main());

        using (var connection = storage.GetConnection())
        {
            connection.Pause(typeof(Program));
            connection.Resume(typeof(Program));
        }
    }
}

You can apply the resulting job filter locally to a type or a method as shown above ([CanBePaused]), or set it globally for all jobs somewhere in initialization logic:

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

Thanks for all your help.

For ease of use we have done this in our application logic by storing a flag in appSettings. We simply check this parameter each time the method is run. We can then toggle this as and when required.

Yep, this implementation is also acceptable. I don’t want to include things that may be implemented as extension filters or in application logic itself. However, most common features should be described on the forum or in the documentation. Perhaps I’ll add a new category tutorial where users would be able to write and find common extensions or ways how to achieve other common tasks. But a bit later.

How do we get the list of paused-jobs?

Good catch, but I’d prefer to be able to pause specific recurring work (using custom recurring job identifier). When I’m inside PerformingContext - I need to pause current recurrent job, matching the given recurring job id…

UPDATE: after some thinking, here’s my implementation covering recurring job ids:

public void OnPerforming(PerformingContext filterContext)
{
    var values = filterContext.Connection.GetAllItemsFromSet("paused-jobs");
    // It's an ordinary job
    if (values.Contains(filterContext.BackgroundJob.Id))
    {
        filterContext.Canceled = true;
        return;
    }
    // Looking for recurring job chains
    foreach (var value in values)
    {
        var recurringJob = filterContext.Connection.GetAllEntriesFromHash($"recurring-job:{value}");
        if (recurringJob != null && recurringJob.TryGetValue("LastJobId", out string jobId) && jobId == filterContext.BackgroundJob.Id)
        {
            filterContext.Canceled = true;
            return;
        }
    }
}

How would that be implemented in the front-end with a button?