I have a Hangfire server set up with several recurring tasks. For local development I don't want these tasks to go through but I need to be able to manually trigger them manually through the Hangfire UI.
I am able to pull the Job Data for the currently running job but I don't see anything within it that tells me if it was manually triggered or not.
Here is an excerpt from my code where RunProcessReportsJob is my RecurringJob in Hangfire
public ExitCodeType RunProcessReportsJob(PerformContext context)
{
var jobId = context.BackgroundJob.Id;
var connection = JobStorage.Current.GetConnection();
var jobData = connection.GetJobData(jobId);
_logger.LogInformation("Reoccurring job disabled.");
return ExitCodeType.NoError;
}
The jobData has a ton of information about the job and context but again I don't see anything within this that tells me if it is a manually triggered job or a scheduled job.
Hope this helps
private bool JobWasManuallyExecuted(string jobId)
{
//'Triggered using recurring job manager' -- Manually triggerd via UI
//'Triggered by recurring job scheduler' -- using scheduller
var jobDetails = JobStorage.Current.GetMonitoringApi().JobDetails(jobId);
if (jobDetails == null)
return false;
return jobDetails.History.ToList().Any(x => x.Reason == "Triggered using recurring job manager");
}
This message appears on the UI as well.
Executed using the scheduler:
Manually executed
Related
I am using Quartz to schedule jobs and use a console application to execute all the jobs.
I currently have 2 console applications which refer to the same set of Quartz tables viz. QRTZ_JOB_DETAILS, QRTZ_TRIGGERS etc.
Due to this, when I execute ConsoleApp1 which doesn't have jobs (created in ConsoleApp2), I get the following error:
XYZ job: Couldn't retrieve job because a required type was not found: Could not load type 'XYZ-Job, XYZ.Job' ---> Quartz.JobPersistenceException:
I have checked the solution here.
Obvious solution is to create separate Quartz table-sets for each console application. That way, I won't get any load type errors.
My question is, in such a scenario, is there a way to get only particular jobs (based on some match), so that I don't need to create 2 table sets.
In the below code, I was thinking if I get all the job names, I will disable the triggers for ConsoleApp2. But then, ConsoleApp2 won't have any jobs to run! (this is because, the tables are same)
Please let me know if there is a better solution.
protected async void StartScheduler1()
{
ISchedulerFactory schedFact = container.ResolveType<ISchedulerFactory>();
var schedTask = schedFact.GetScheduler();
schedTask.Wait();
scheduler = schedTask.Result;
var jobs = new List<JobKey>();
foreach (var group in scheduler.GetJobGroupNames().Result)
{
var groupMatcher = GroupMatcher<JobKey>.GroupContains(#group);
foreach (var jobKey in scheduler.GetJobKeys(groupMatcher).Result)
{
jobs.Add(jobKey);
}
}
scheduler.Start().Wait();
}
Finally found a solution. There is a column called Sched_Name in Quartz tables. This column is used by Quartz scheduler to get job details.
Using this column, we can have numerous different groups in the same Quartz tables. There is no need create separate Quartz table-sets.
For e.g.
SELECT * FROM QRTZ_JOB_DETAILS WHERE SCHED_NAME = 'CESA'
SELECT * FROM QRTZ_JOB_DETAILS WHERE SCHED_NAME = 'CESB'
I have a .Net application that utilizes multiple Hangfire servers.
I want to be able to have one Hangfire RecurringJob trigger multiple BackgroundJobs that can be picked up by any available server. Currently whenever I schedule Background Jobs from a Hangfire Job only the server that scheduled them will process them.
For example, I have 5 Hangfire Servers and 10 tasks.
I would want there to be 2 tasks on each Hangfire server, instead I am seeing 1 server with 10 tasks and 4 with 0.
So again I have 5 Hangfire servers, all using the same database, and 1 RecurringJob, this RecurringJob just reads some files and enqueues several background jobs.
foreach (var file in reportSourceSetFileList)
{
_logger.LogInformation($"Queuing Background job for: {file}");
var backgroundJobId = BackgroundJob.Enqueue<IJobHandler>(job => job.ProcessFile(file, files, null));
}
However, only the Hangfire Server that ran the RecurringJob will process the Enqueued job.
How can I have those Enqueued jobs be processed by any of my 5 Hangfire Servers and not just the one that queued them?
There is no built in functionality in Hangfire to use a round robin type load balancer between multiple hangfire servers.
My solution was to use the Queuing system. When each Hangfire server starts they are given a task identifier, which is a GUID, I also add a unique queue to that server which uses the same GUID as its name.
So each server will look at 2 queues, Default and GUID.
Then I use the following code to find which server has the least jobs currently processing.
private string GetNextAvailableServer()
{
var serverJobCounts = new Dictionary<string, int>();
//get active servers
var serverList = JobStorage.Current.GetMonitoringApi().Servers();
foreach (var server in serverList)
{
if (server.Heartbeat.Value > DateTime.Now.AddMinutes(-1))
{
serverJobCounts.Add(server.Name, 0);
foreach (var queue in server.Queues)
{
var currentQueues = JobStorage.Current.GetMonitoringApi().Queues();
serverJobCounts[server.Name] += (int?)currentQueues.FirstOrDefault(e => e.Name == queue)?.Length ?? 0;
}
}
}
var jobs = JobStorage.Current.GetMonitoringApi().ProcessingJobs(0, int.MaxValue);
foreach (var job in jobs)
{
if (serverJobCounts.ContainsKey(job.Value.ServerId))
{
serverJobCounts[job.Value.ServerId] += 1;
}
}
var nextServer = serverJobCounts.OrderBy(e => e.Value).FirstOrDefault().Key;
return nextServer.Split(':')[0].Replace("-", string.Empty, StringComparison.InvariantCulture);
}
This returns the GUID of the server that has the least jobs, which is also the name of the Queue. Therefore you can schedule the next job to the specific queue with the least jobs currently processing.
var nextServer = GetNextAvailableServer();
var client = new BackgroundJobClient();
var state = new EnqueuedState(nextServer);
var enqueueJob = client.Create<IJobHandler>(job => job.ProcessFile(file, files, null), state);
Additionally when I wrote this Hangfire didn't allow for hyphens in a queue name, hence my string manipulation to make the GUIDs work. I think the newest version of hangfire lets you use hyphens in the name.
One thing to look out for, this solution breaks when one of you server dies. Since a job is given a unique Queue if the server watching that queue dies before processing the job it will never be picked up.
I have a situation where I need a recurring job registered with hangfire to run on every server in the cluster.
(The job is to copy some files locally so needs to run on every server regularly)
So far I have tried registering the same job with an id of the server name resulting in n job for n servers:
RecurringJob.AddOrUpdate(Environment.MachineName, () => CopyFiles(Environment.MachineName), Cron.MinuteInterval(_delay));
and the job itself checks if it is the correct server and only does something if it is:
public static void CopyFiles(string taskId)
{
if (string.IsNullOrWhiteSpace(taskId) || !taskId.Equals(Environment.MachineName))
{
return;
}
// do stuff here if it matches our taskname
}
The problem with this is that all jobs executes on the first server to come along, is marked as complete and as a result is not executed by the other servers.
Is there any way to ensure that the job runs on all servers?
or is there a way to ensure that only one server can process a given job? i.e. target the job at the server that created it
Found an answer using this link.
Simply assign the job to a queue that is specific to the server you want it processing on.
So I changed my enqueue to:
RecurringJob.AddOrUpdate(Environment.MachineName,
() => CopyFiles(Environment.MachineName),
Cron.MinuteInterval(_delay),
queue: Environment.MachineName.ToLower(CultureInfo.CurrentCulture));
And when I start my server I do this:
_backgroundJobServer = new BackgroundJobServer(new BackgroundJobServerOptions
{
Queues = new[] { Environment.MachineName.ToLower() }
});
What I have is an AJAX form on a View that makes a call to the server. This call perform n number of tasks where n is a number decided by records in a database (typically no more than 10 records). Each record corresponds to a Build Definition in TFS so what I am trying to do is get all of these Build Definitions, queue them in TFS, and as each build completes update the UI so that user knows which builds have completed.
Unfortunately I am not sure about how best to do this. I was thinking something along these lines:
foreach (var dep in builds)
{
TFS tfsServer = new TFS(TFS_SERVER_ADDRESS);
IBuildServer buildServer;
int id = tfsServer.QueuBuild(dep.TeamProject, dep.BuildDefinition);
string teamProject = dep.TeamProject;
Task.Factory.StartNew(() => GetBuildStatus(teamProject, id, tfsServer));
}
The task that is called is:
private void GetBuildStatus(string TeamProject, int BuildID, TFS Server)
{
Server.GetBuildStatus(TeamProject, BuildID);
AsyncManager.OutstandingOperations.Decrement();
}
The problem here is that my Completed method isn't going to get called until all of the builds have completed. How would I go about feeding data back up to the UI a piece at a time?
It is also worth mentioning that the GetBuildStatus method looks like this:
do
{
var build = buildsView.QueuedBuilds.FirstOrDefault(x => x.Id == BuildID);
if(build != null)
{
status = build.Status;
detail = build.Build;
}
} while (status != QueueStatus.Completed);
return detail.Status.ToString();
Given that the duration of a build will be longer than the timeout for an HTTP request you cannot leave the browser waiting while this happens. You need to return a page and then poll for updates from that page using AJAX. Typically you'd have a timer in javascript that triggers a regular call back to the server to get the updated status information.
But, since you are using .NET you could also consider trying SignalR which lets you use long polling, server sent events or web sockets to wait for updates from the server and it wraps it all up in some easy to implement .NET classes and Javascript.
I have to create administration page of all scheduled jobs and triggers. How can i get details of running jobs and triggers in Quartz.NET? Can I pause/stop or update jobs? Is there any sample code?
Here is how you would go about it using the StdSchedulerFactory
ISchedulerFactory schedFact = new StdSchedulerFactory();
foreach (IScheduler scheduler in schedFact.AllSchedulers)
{
var scheduler1 = scheduler;
foreach (var jobDetail in from jobGroupName in scheduler1.JobGroupNames
from jobName in scheduler1.GetJobNames(jobGroupName)
select scheduler1.GetJobDetail(jobName, jobGroupName))
{
//Get props about job from jobDetail
}
foreach (var triggerDetail in from triggerGroupName in scheduler1.TriggerGroupNames
from triggerName in scheduler1.GetTriggerNames(triggerGroupName)
select scheduler1.GetTrigger(triggerName, triggerGroupName))
{
//Get props about trigger from triggerDetail
}
}
Here an open project that does just this.
The project should have all the code you need to create you own, or you can just use the open source project.
Web Based admin page for Quartz.net
Allow registering of existing Quartz.net installations
Allow viewing of Jobs and Triggers
Allow scheduling of Jobs including editing JobDataMaps
Allow viewing of calendars
Allow viewing of trigger fire times
Silverlight based timeline showing upcoming schedules