HangfireIO / Hangfire

An easy way to perform background job processing in .NET and .NET Core applications. No Windows Service or separate process required
https://www.hangfire.io
Other
9.42k stars 1.7k forks source link

How does Hangfire allow configuring discarding delayed jobs' disposal if overdue #1170

Open svncsvr opened 6 years ago

svncsvr commented 6 years ago

I have been reading Hangfire documentation and various post in forums to find a way setting delayed tasks not to be executed if overdue.I couldn't find a solution so far. ScheduleServer may be closed or unavailable for some time and when it becomes available again, if the delayed tasks are overdue I want them to be deleted and be able to configure the overdue threshold. Thank you.

pieceofsummer commented 6 years ago

You can create an IElectStateFilter to cancel job execution (move it from Scheduled to Deleted instead of Enqueued state) if the difference between scheduled time and the current time is larger than the certain threshold.

svncsvr commented 6 years ago

Thank you for the advice I started implementing the filter and I need to intercept state change from scheduled to En-queued to be able to interrupt execution if threshold is already passed.Since CurrentState property in context object is string type, I cant find schedule-Date to make the math for setting its state to deleted.

I can't trust the date on which OnStateElection method called either bc the scheduler server may have been unavailable at the scheduledAt date. So I must somehow get original scheduledAt value. A workaround would be keeping jobIds and ScheduleAt values in a dictionary when candidate state is scheduled for the job, but I don't really wanna use custom logic.

pieceofsummer commented 6 years ago

Try something like

class DelayedJobTimeoutFilterAttribute : JobFilterAttribute, IElectStateFilter
{
    private static readonly TimeSpan TimeoutValue = TimeSpan.FromMinutes(30);

    public void OnStateElection(ElectStateContext context)
    {
        // we're only interested in transitions from Scheduled state
        if (context.CurrentState != ScheduledState.StateName) return;

        // we're only interested in transitions to Enqueued state
        if (context.CandidateState.Name != EnqueuedState.StateName) return;

        var state = context.Connection.GetStateData(context.BackgroundJob.Id);
        if (state == null) return; // just in case

        string enqueueAtStr;
        if (state.Data.TryGetValue("EnqueueAt", out enqueueAtStr))
        {
            var enqueueAt = JobHelper.DeserializeDateTime(enqueueAtStr);
            if (DateTime.UtcNow - enqueueAt > TimeoutValue)
            {
                context.CandidateState = new DeletedState
                {
                    Reason = "Too late to perform delayed job"
                };
            }
        }
    }
}
svncsvr commented 6 years ago

This may not work as intended

Server and client are different applications just so you know

Lets suppose Timeout is 5 minutes, Job 'SendMail' is Scheduled for 17:00
Server is down(for some reason) and wakes up at 17:30 As soon as server is up SendMail job is enqueued , be 17:31 Since (Now- EnqueuedAt) < Timeout is true Even though this task is 30 minutes overdue it will still execute.

burningice2866 commented 6 years ago

@svncsvr consider looking at the "ScheduledAt" Data key as well then.

svncsvr commented 6 years ago

During a debug session I noticed that EnqueuedAt property is actually set from the client (I assume) and its value is very close to ScheduledAt, there is only a few seconds difference (as much as how often check schedule jobs daemon performs checks, a few seconds in my configuration) so in this case your code works, just to make sure I can change EnqueuedAt with ScheduledAt as you pointed out.That state data is available too. Thank you so much for your time and afford.

pieceofsummer commented 6 years ago

Don't quite get your point.

Let's say, you call BackgroundJob.Schedule(..., TimeSpan.FromMinutes(30)) at 16:30, so it should be executed at 17:00. The job will be created in Scheduled state with ScheduledAt = 16:30 and EnqueueAt = 17:00.

But the server is down until 17:30, and then it wakes up and attempts to enqueue the scheduled job. The moment the filter is executed, Now - EnqueueAt should be 30 minutes. So if you set 5 min timeout, 30 would be greater than 5, and the job will be transitioned into Deleted state.

Isn't this what you want?

Server and client are different applications just so you know

This doesn't matter.

svncsvr commented 6 years ago

I would say ScheduledAt would be 17:00 in that case, now it makes sense I misinterpreted the variable name.Thank you for the clarification.