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.44k stars 1.71k forks source link

Recurring job '?' can not be scheduled due to job load exception. #595

Open gandarez opened 8 years ago

gandarez commented 8 years ago

Talking about Scalability/Distrubted Systems I think each server might see only its queues. In my production envrionment I'm running two instances in two servers. Each server actually runs over different queues as below image

Server "A" has only libraries needed there and in the other server ("B") only the libraries needed also. When server "B" tries to Schedule a job it throws JobLoadException because there's no library into its binary folder. What I suggest is to change few classes to restrict RecurringJobScheduler class to watch only the right queues.

The queue is already in the context and could be used to filter recurring-jobs as well. image

jmvermeulen commented 8 years ago

Having the exact same issue. To me this is clearly a bug, can anyone confirm?

RupertAvery commented 8 years ago

Also encountered this. Although it is merely a warning, it is unnecessarily cluttering the logs.

Also, can the Queue attribute be used for recurring jobs as well?

LucasFarley commented 5 years ago

There is solution for this problem? I am facing the same problem.

ZlobnyiSerg commented 4 years ago

+1 for this

berdem commented 4 years ago

+111

GeXiaoguo commented 4 years ago

Here is a solution.

Deantwo commented 4 years ago

Here is a solution.

I would call it a workaround, not a solution. The bug needs to be fixed in Hangfire and not ignored for another 4 years.

But thanks for sharing.

GeXiaoguo commented 4 years ago

Here is a solution.

I would call it a workaround, not a solution. The bug needs to be fixed in Hangfire and not ignored for another 4 years.

But thanks for sharing.

Yes, it is a workaround. This problem has been reported by many people for many years now. Nothing has happened yet. I am losing hope.

udlose commented 4 years ago

any update on this (over 4 years now)!

christopher-bonitz commented 3 years ago

No fixes yet, I just hit this issue today running on MongoDB. Now I have to run a seperate hangfire application (dashboard, server and database) just to run my "report" queue. @odinserj any chance you can fix this with your magic fingers?

odinserj commented 3 years ago

If this problem appears when using the same job storage for multiple applications with different code base, the simplest solution would be to use some shared assembly referenced from those multiple applications and put background job class and method (or at least their interfaces) there.

Recurring job schedule itself doesn't depend on the queue name, and when wrong server tries to create and schedule such a recurring job, it fails. And it is important to have access to the declared method, because filters in Hangfire can significantly change the background processing workflow.

I'm aware of this problem and really sorry there were a lot of troubles with it. I was trying to solve it many times and looks like it will be finally possible to do this after 1.8.0 is released with some tricks and not so hard limitations – by creating a shared assembly that will accept everything including filters dynamically and releasing this as an extension.

But currently the only workaround is to use some shared assembly referenced by all of the projects.

christopher-bonitz commented 3 years ago

If this problem appears when using the same job storage for multiple applications with different code base, the simplest ... But currently the only workaround is to use some shared assembly referenced by all of the projects.

You are honestly a fantastic and helpful person. I have begun the code migration to a shared space, crossing fingers for a fix in 1.8.0.

afonsoft commented 2 years ago

My suggestion is to create a JobFilter to do the Queue and Retry treatment together. The problem is in the ScheduledState class that does not use the Queue, while the EnqueuedState class has the Queue. I created an Attribute with the name AutomaticRetryInQueueAttribute, but it does not have the time waiting to boot the next job.

Don't have delay between in retry.

` public class AutomaticRetryInQueueAttribute : JobFilterAttribute, IApplyStateFilter, IElectStateFilter { private string queue; private int attempts; private readonly object _lockObject = new object();

    private readonly ILog _logger = LogProvider.For<AutomaticRetryInQueueAttribute>();

    public AutomaticRetryInQueueAttribute()
    {
        queue = "Default";
        attempts = 10;
    }

    public int Attempts
    {
        get { lock (_lockObject) { return attempts; } }
        set
        {
            if (value < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(value), @"Attempts value must be equal or greater than zero.");
            }

            lock (_lockObject)
            {
                attempts = value;
            }
        }
    }

    public string Queue
    {
        get { lock (_lockObject) { return queue; } }
        set
        {
            lock (_lockObject)
            {
                queue = value;
            }
        }
    }

    public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
    {
        var newState = context.NewState as EnqueuedState;
        if (!string.IsNullOrWhiteSpace(queue) && newState != null && newState.Queue != Queue)
        {
            newState.Queue = String.Format(Queue, context.BackgroundJob.Job.Args.ToArray());
        }

        if (context.NewState is ScheduledState &&
            context.NewState.Reason != null &&
            context.NewState.Reason.StartsWith("Retry attempt"))
        {
            transaction.AddToSet("retries", context.BackgroundJob.Id);
        }
    }

    public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
    {
        if (context.OldStateName == ScheduledState.StateName)
        {
            transaction.RemoveFromSet("retries", context.BackgroundJob.Id);
        }
    }

    public void OnStateElection(ElectStateContext context)
    {
        var failedState = context.CandidateState as FailedState;
        if (failedState == null)
        {
            // This filter accepts only failed job state.
            return;
        }

        var retryAttempt = context.GetJobParameter<int>("RetryCount") + 1;

        if (retryAttempt <= Attempts)
        {
            ScheduleAgainLater(context, retryAttempt, failedState);
        }
        else
        {
            _logger.ErrorException($"Failed to process the job '{context.BackgroundJob.Id}': an exception occurred.", failedState.Exception);
        }
    }

    private void ScheduleAgainLater(ElectStateContext context, int retryAttempt, FailedState failedState)
    {
        context.SetJobParameter("RetryCount", retryAttempt);

        const int maxMessageLength = 50;
        var exceptionMessage = failedState.Exception.Message.Length > maxMessageLength
            ? failedState.Exception.Message.Substring(0, maxMessageLength - 1) + "…"
            : failedState.Exception.Message;

        // If attempt number is less than max attempts, we should
        // schedule the job to run again later.

        var reason = $"Retry attempt {retryAttempt} of {Attempts}: {exceptionMessage}";

        context.CandidateState = (IState)new EnqueuedState
        {
            Reason = reason,
            Queue = String.Format(Queue, context.BackgroundJob.Job.Args.ToArray())
        };

        _logger.WarnException($"Failed to process the job '{context.BackgroundJob.Id}': an exception occurred. Retry attempt {retryAttempt} of {Attempts} will be performed.", failedState.Exception);
    }
}

`

christopher-bonitz commented 2 months ago

My suggestion is to create a JobFilter to do the Queue and Retry treatment together. The problem is in the ScheduledState class that does not use the Queue, while the EnqueuedState class has the Queue. I created an Attribute with the name AutomaticRetryInQueueAttribute, but it does not have the time waiting to boot the next job.

Don't have delay between in retry.

` public class AutomaticRetryInQueueAttribute : JobFilterAttribute, IApplyStateFilter, IElectStateFilter { private string queue; private int attempts; private readonly object _lockObject = new object();

    private readonly ILog _logger = LogProvider.For<AutomaticRetryInQueueAttribute>();

    public AutomaticRetryInQueueAttribute()
    {
        queue = "Default";
        attempts = 10;
    }

    public int Attempts
    {
        get { lock (_lockObject) { return attempts; } }
        set
        {
            if (value < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(value), @"Attempts value must be equal or greater than zero.");
            }

            lock (_lockObject)
            {
                attempts = value;
            }
        }
    }

    public string Queue
    {
        get { lock (_lockObject) { return queue; } }
        set
        {
            lock (_lockObject)
            {
                queue = value;
            }
        }
    }

    public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
    {
        var newState = context.NewState as EnqueuedState;
        if (!string.IsNullOrWhiteSpace(queue) && newState != null && newState.Queue != Queue)
        {
            newState.Queue = String.Format(Queue, context.BackgroundJob.Job.Args.ToArray());
        }

        if (context.NewState is ScheduledState &&
            context.NewState.Reason != null &&
            context.NewState.Reason.StartsWith("Retry attempt"))
        {
            transaction.AddToSet("retries", context.BackgroundJob.Id);
        }
    }

    public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
    {
        if (context.OldStateName == ScheduledState.StateName)
        {
            transaction.RemoveFromSet("retries", context.BackgroundJob.Id);
        }
    }

    public void OnStateElection(ElectStateContext context)
    {
        var failedState = context.CandidateState as FailedState;
        if (failedState == null)
        {
            // This filter accepts only failed job state.
            return;
        }

        var retryAttempt = context.GetJobParameter<int>("RetryCount") + 1;

        if (retryAttempt <= Attempts)
        {
            ScheduleAgainLater(context, retryAttempt, failedState);
        }
        else
        {
            _logger.ErrorException($"Failed to process the job '{context.BackgroundJob.Id}': an exception occurred.", failedState.Exception);
        }
    }

    private void ScheduleAgainLater(ElectStateContext context, int retryAttempt, FailedState failedState)
    {
        context.SetJobParameter("RetryCount", retryAttempt);

        const int maxMessageLength = 50;
        var exceptionMessage = failedState.Exception.Message.Length > maxMessageLength
            ? failedState.Exception.Message.Substring(0, maxMessageLength - 1) + "…"
            : failedState.Exception.Message;

        // If attempt number is less than max attempts, we should
        // schedule the job to run again later.

        var reason = $"Retry attempt {retryAttempt} of {Attempts}: {exceptionMessage}";

        context.CandidateState = (IState)new EnqueuedState
        {
            Reason = reason,
            Queue = String.Format(Queue, context.BackgroundJob.Job.Args.ToArray())
        };

        _logger.WarnException($"Failed to process the job '{context.BackgroundJob.Id}': an exception occurred. Retry attempt {retryAttempt} of {Attempts} will be performed.", failedState.Exception);
    }
}

`

@odinserj did you see this suggestion?

3 years ago, we went with the shared code-space but it can no longer be maintained, due to expanding projects are starting to overlap. A permanent solution would be quite nice, we're running 1.8.14 and the problem is still very much real :)