Hi
In version 1.3.3.0 I am experience an strange behaviour which is as follows.
I have two separated physical services and each of them belongs to an specific queue.
everything works perfectly fine when there is no exception, however when an exception happens, the system reschedule the job for the default Queue which neither of services is listening to.
I would be thankful if you can let me know if this is a bug on that version or it can be handle somehow!
As the name of queue is vary , I have tried JobFilterAttribute and other interceptors, however the job is strangely being assigned into a DEFAULT queue which technically neither of services are listening to.
Do you happen to use BackgroundJobClient.Create() rather than BackgroundJobClient.Enqueue()? I had the same problem last time, and I found out that ONLY when you use Enqueue Hangfire will respect the original queue name during the retry.
Unfortunately once a job is being failed, it re queue it to Default Queue even I don’t have this Queue.
This is the code I am using and it is still causing same problem…
var client = new BackgroundJobClient();
var state = new EnqueuedState(queueName);
var result=client.Create(()=>JobProcessor.Execute(value), state);
I don’t like having to decorate my methods with attributes so have looked at using Job Parameters
Using a Job Filter, detect when the job is being enqueued then store the queuename in the job parameter.
When a retry occurs it will be enqueued again, so we can set the queue name at the point it is enqueued
In your filter, implement Hangfire.States.IElectStateFilter
public void OnStateElection(Hangfire.States.ElectStateContext context)
{
var enqueuedState = context.CandidateState as Hangfire.States.EnqueuedState;
if (enqueuedState != null)
{
var qn = context.GetJobParameter<string>("QueueName");
if (!String.IsNullOrWhiteSpace(qn))
{
enqueuedState.Queue = qn;
}
else
{
context.SetJobParameter("QueueName", enqueuedState.Queue);
}
}
}
Still learning hangfire so interested to know of any drawbacks to this approach
Though this thread is pretty old, adding solution I found out for Hangfire with dotnet core, so it might help somebody.
if you are using Aspnet core 3.1, add below filter for hangfire queues:
public class PreserveOriginalQueueAttribute : JobFilterAttribute, IApplyStateFilter, IElectStateFilter
{
public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
var id = context.BackgroundJob.Id;
var state = context.Connection.GetStateData(id);
var initialQueue = context.Connection.GetJobParameter(id, JobParam.InitialQueue);
state.Data.TryGetValue(Constants.Queue, out string name);
if (string.IsNullOrEmpty(initialQueue))
{
context.Connection.SetJobParameter(id, JobParam.InitialQueue, name);
}
if(state.Name.Equals(States.Scheduled) && state.Reason.StartsWith(Reason.Retry))
{
//FailedState
if(context.NewState is EnqueuedState newState)
{
newState.Queue = initialQueue;
context.Connection.SetJobParameter(context.BackgroundJob.Id, JobParam.QueueReason, Reason.Retry);
}
}
if(context.NewState is ProcessingState processingState)
{
//Remove QueueReason Param for subsequent manual triggers.
context.Connection.SetJobParameter(context.BackgroundJob.Id, JobParam.QueueReason, null);
}
}
public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
}
/// For changing state from default to original queue for manual trigger
public void OnStateElection(ElectStateContext context)
{
var initialQueue = context.Connection.GetJobParameter(context.BackgroundJob.Id, JobParam.InitialQueue);
var queueReason = context.Connection.GetJobParameter(context.BackgroundJob.Id, JobParam.QueueReason);
if (context.CurrentState.Equals(States.Enqueued) && !string.IsNullOrEmpty(initialQueue) && string.IsNullOrEmpty(queueReason))
{
context.Connection.SetJobParameter(context.BackgroundJob.Id,JobParam.QueueReason, Reason.Trigger);
context.CandidateState = new EnqueuedState(initialQueue);
}
}
}
static internal class States
{
public static readonly string Enqueued = "Enqueued";
public static readonly string Scheduled = "Scheduled";
}
static internal class JobParam
{
public static readonly string InitialQueue = "InitialQueue";
public static readonly string QueueReason = "QueueReason";
}
static internal class Reason
{
public static readonly string Trigger = "Trigger";
public static readonly string Retry = "Retry";
}
You can add this filter in startup.cs:
GlobalConfiguration.Configuration.UseActivator(new CustomHangfireActivator(serviceProvider))
.UseFilter(new PreserveOriginalQueueAttribute());
This will switch to original queues for failed jobs… while triggering job from dashboard, this will just change state to default and instantly change to original queue.