Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hangfire runs missed Jobs after server wakes up, can it be prevented? #620

Closed
evaldas-raisutis opened this issue Jul 20, 2016 · 11 comments
Closed

Comments

@evaldas-raisutis
Copy link

By default, it seems, hangfire will attempt to run jobs it has missed, if for example, server was down when jobs were supposed to be run. I would like to disable this functionality. We have jobs which import products into a webshop, and for the duration of an import site becomes quite unresponsive due to resources used by the import.

Problem is we only want to run jobs at night OR when triggered manually through UI. Can I disable hangfire trying to run missed jobs after startup?

@darrenmeehan
Copy link

This is also something I've to look into further, also with an import job.

@DBalashov
Copy link

I also can't found solution, but insert additional checking in start of job for time. For example, if job scheduled at 3:00 AM - I'm check current time inside job for 3:00 AM +/- 10 minutes. Job aborted if difference more than 10 minutes.

@evaldas-raisutis
Copy link
Author

@DBalashov that works on scheduled executions, but I also want to be able to execute the job manually when necessary.

@evaldas-raisutis
Copy link
Author

It seems on coming back from idle, hangfire will grab jobs which were to be executed in the past and sets NextExecution to Now. It would be nice if there was a setting to prevent this.

@enrichz
Copy link

enrichz commented Jun 20, 2018

Any update on this?

@colinblaise
Copy link

colinblaise commented Feb 21, 2019

This feature is also a requirement for my use case. Jobs are expected to ONLY run at the designated time. If the server was down at the designated time or it failed then it cannot be re-run at any later date. period.

I thought this would have sufficed, but it does not appear to stop jobs from automatically starting on startup.

GlobalJobFilters.Filters.Add(new AutomaticRetryAttribute { Attempts = 0 });

@pieceofsummer
Copy link
Contributor

pieceofsummer commented Feb 22, 2019

The recurring job scheduler only checks if there should be a scheduled execution since the previous job run, and triggers the job if needed. It doesn't care if the scheduled time is missed for 1 second or a few days.

I'd try to go with a client filter that would cancel "late" jobs:

public class NoMissedRunsAttribute : JobFilterAttribute, IClientFilter
{
    public TimeSpan MaxDelay { get; set; } = TimeSpan.FromMinutes(15);
    
    public void OnCreating(CreatingContext context)
    {
        if (context.Parameters.TryGetValue("RecurringJobId", out var recurringJobId) &&
            context.InitialState?.Reason == "Triggered by recurring job scheduler")
        {
            // the job being created looks like a recurring job instance,
            // and triggered by a scheduler (i.e. not manually) at that.
            
            var recurringJob = context.Connection.GetAllEntriesFromHash($"recurring-job:{recurringJobId}");
            
            if (recurringJob != null && recurringJob.TryGetValue("NextExecution", out var nextExecution))
            {
                // the next execution time of a recurring job is updated AFTER the job instance creation,
                // so at the moment it still contains the scheduled execution time from the previous run.
                
                var scheduledTime = JobHelper.DeserializeDateTime(nextExecution);
                
                if (DateTime.UtcNow > scheduledTime + MaxDelay)
                {
                    // the job is created way later than expected
                    context.Canceled = true;
                }
            }
        }
    }

    public void OnCreated(CreatedContext context)
    {
    }
}

I haven't tested it though, so it may require some adjustments before it actually works :)

@5p1k3md
Copy link

5p1k3md commented Aug 25, 2019

Hello,

i just ried the solution from @pieceofsummer .
Unfortunately there is no initial state given with the compared string given at any time.
Is there an other way to determ a rerunning recurring job after server restart?

@Sephiii
Copy link

Sephiii commented Jan 4, 2021

The only way that i found it's to recreate jobs at startup

     using (var connection = JobStorage.Current.GetConnection())
        {
            foreach (var recurringJob in connection.GetRecurringJobs())
            {              
                    var jobTimeZone = TimeZoneInfo.FindSystemTimeZoneById(recurringJob.TimeZoneId) ?? TimeZoneInfo.Utc;
                    var name = recurringJob.Id;
                     
                    RecurringJob.RemoveIfExists(recurringJob.Id);

                    var manager = new RecurringJobManager();

                    var job = new Hangfire.Common.Job(recurringJob.Job.Type, recurringJob.Job.Method, recurringJob.Job.Args);

                    manager.AddOrUpdate(name, job, recurringJob.Cron, jobTimeZone, recurringJob.Queue);
            }
        }

`

@nmangue
Copy link

nmangue commented Jan 5, 2021

I have tweaked @pieceofsummer code to get some state from the call stack instead of the filter context.
Here is the code I came up with:

using System;
using System.Diagnostics;
using System.Linq;
using Hangfire.Client;
using Hangfire.Common;
using Hangfire.Server;

public class NoMissedRunsAttribute : JobFilterAttribute, IClientFilter
{
  public int MaxDelayMs { get; set; } = (int) TimeSpan.FromMinutes(1).TotalMilliseconds;

  public void OnCreating(CreatingContext filterContext)
  {
    if (filterContext.Parameters.TryGetValue("RecurringJobId", out var recurringJobId))
    {
      // the job being created looks like a recurring job instance.

      var recurringJob = filterContext.Connection.GetAllEntriesFromHash($"recurring-job:{recurringJobId}");

      if (recurringJob != null && recurringJob.TryGetValue("NextExecution", out var nextExecution))
      {
        var utcNow = DateTime.UtcNow;
    
        // the next execution time of a recurring job is updated AFTER the job instance creation,
        // so at the moment it still contains the scheduled execution time from the previous run.
        var scheduledTime = JobHelper.DeserializeDateTime(nextExecution);

        // Check if the job is created way later than expected
        // and if it was created from the scheduler.
        if (utcNow > scheduledTime.AddMilliseconds(MaxDelayMs) && IsCreatedFromRecurringJobScheduler())
        {
          filterContext.Canceled = true;
        }
      }
    }
  }

  private static bool IsCreatedFromRecurringJobScheduler()
  {
    // Get call stack
    var stackTrace = new StackTrace();
    return stackTrace.GetFrames().Any(f => f.GetMethod()?.DeclaringType == typeof(RecurringJobScheduler));
  }

  public void OnCreated(CreatedContext filterContext)
  {
    // Nothing to do.
  }
}

@Fungusware
Copy link

Has this been integrated, this feature to NOT run missed Jobs is essential for both of my current clients requirements.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests