I use Quartz.net to create 5 Schedulers in a Windows Service.
But when I want to shutdown one of my Schedulers, it shut them all.
Here is my class :
internal class Scheduler
{
private IScheduler shed;
public SchedulerConfig config { get; private set; }
internal Scheduler(SchedulerConfig config)
{
this.config = config;
}
internal void Schedule()
{
ISchedulerFactory sf = new Quartz.Impl.StdSchedulerFactory();
shed = sf.GetScheduler();
Type T = config.job;
IJobDetail job = JobBuilder.Create(T)
.WithIdentity("job_" + config.name, "groupScheduler")
.Build();
string cronExpression = config.cronExpression;
ICronTrigger trigger = (ICronTrigger)TriggerBuilder.Create()
.WithIdentity("trigger_" + config.name, "groupScheduler")
.WithCronSchedule(cronExpression)
.Build();
job.JobDataMap.Put("SchedulerConfig", config);
DateTimeOffset ft = shed.ScheduleJob(job, trigger);
shed.Start();
}
internal void Stop()
{
if (shed != null && !shed.IsShutdown)
{
shed.Shutdown(false);
shed = null;
}
}
}
The schedulers use a differant 'SchedulerConfig', with a differant 'Name'.
Is it because they all have the same Group Name ?
When i call the 'Stop' method, the IShcheduler of this object is null, but the others are shutdown too.
Before the call, they all have :
IsStandByMode = false
IsShutdown = false
IsStarted = true
After the call, one is null and the 4 others have :
IsStandByMode = true
IsShutdown = true
IsStarted = true
Do you have any idea to find the solution about this problem ?
Thanks,
When you create scheduler instance you should give it unique instance name. This way you can shutdown specific scheduler and not all of them. Check StdSchedulerFactory.GetScheduler method sources and you will understand everything.
var props = new NameValueCollection
{
{"quartz.scheduler.interruptJobsOnShutdownWithWait", "true"},
{"quartz.scheduler.instanceName", nameof(CheckPricesScheduler)}
};
var schedulerFactory = new StdSchedulerFactory(props);
If you go through quartz documentation you will find that shed.Shutdown(false); is not the correct way to stop some particular trigger or job.Shutdown method will Halts the Scheduler's firing of Triggers, and cleans up all resources associated with the Scheduler.
so to stop individual jobs or trigger please use pauseJob(JobKey jobKey),pauseJobs(GroupMatcher<JobKey> matcher),pauseTrigger(TriggerKey triggerKey) and pauseTriggers(GroupMatcher<TriggerKey> matcher) methods
i have referred java docs for the above function please look for similar method for c# library for quartz project.
Related
We're trying to get the Hangfire ServerName from a running background job. Is this possible and if so, how?
So far, we've looked at the ProcessContext object, but it doesn't seem to include the ServerName.
Thank you for the help.
A blind shot (not tested), but I would try something like this with an IApplyStateFilter :
public class GetServerIdFilter : IApplyStateFilter
{
public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
var state = context.NewState as ProcessingState;
if (state != null)
{
var serverId = state.ServerId;
var workerId = state.WorkerId;
}
}
public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
}
}
The filter can be registered as in this answer
I know the following doesn't quite answer your question about reading it from a job, as far as I've seen it looks impossible. If it is impossible, this answer might be helpful, so here you go.
I'm not sure there is an option to read it from somewhere.
Anyway, the Hangfire docs here read you could handle the server name manually:
Since the defaults values provide uniqueness only on a process level, you should handle it manually if you want to run different server instances inside the same process:
var options = new BackgroundJobServerOptions
{
ServerName = String.Format(
"{0}.{1}",
Environment.MachineName,
Guid.NewGuid().ToString())
};
var server = new BackgroundJobServer(options);
// or
app.UseHangfireServer(options);
I'm working with Quartz scheduler, and attempting to close all jobs on shutdown of the app. I have one specialty job that does a 'Hold' or 'Busy-wait', basically until it gets a condition it sits there waiting patiently.
This job is new, due to a new integration point. The app is run as a service using Topshelf, and whenever we try to shutdown the service to upgrade it, now that this job is running, we have to end up restarting the server to get it to shutdown.
Anyhow, here it gets weird, I have a single jobtype, and when i try to trigger the interrupt in the following section of code using the jobs FireInstanceId or JobKey:
_logger.InfoFormat("{0} scheduler interrupting listener", scheduler.SchedulerName);
scheduler.Interrupt(ListenerKeys.Realtime);
_logger.InfoFormat("{0} scheduler shutting down", scheduler.SchedulerName);
scheduler.Shutdown(true);
_logger.InfoFormat("{0} scheduler shut down", scheduler.SchedulerName);
I get an exception:
Job 'Listeners.Realtime' can not be interrupted, since it does not implement Quartz.IInterruptableJob
One would assume this is straight forward. However Here is the ONLY job that uses this job key:
ListenerJob : BaseJob, IInterruptableJob
{
// some other code referenced in ExecuteJob
public void Interrupt()
{
_dequeuer.StopDequeing();
}
}
I'd go out on a limb and say that's how you implement it, so my question becomes: is there a known bug in Quartz? Is there an issue with group-keys and interrupts maybe? Is there just a way to tell the scheduler to interrupt ALL jobs that are interruptable? Is there an alternative?
UPDATE
I decided to run the following code for more diagnostics from below answers. var interfaces does in fact include IInterruptableJob
var jobs = scheduler.GetCurrentlyExecutingJobs().Where(x => Equals(x.JobDetail.Key, ListenerKeys.Realtime));
var job1 = jobs.First();
var interfaces = job1.JobDetail.JobType.GetInterfaces();
Additionally, I ran ReportInterruptableJob as suggested below, which checked the assembly and confirmed ListenerJob implements the interface.
UPDATE2:
Ok, I went out to git hub, and ran the exact meshos. Job.JobInstance as IInterruptableInterface returns null, which is why I get the error. What I don't understand I guess, I how the JobInstance is formed around the IJo which does implement IInterruptableJob
UPDATE3: Ok.... So I found something in the bootstrap that is using JobWrapper<>. I know nothing about it, but Im sure that is part of it.
So I agree with the other Answer (C Knight) that the JobKey may be off.
If you've implemented the interface...and you have the right JobKey..then you should not get that exception.
Below is the code I have for the interrupting a job. I'm trying to find the "findthekey" logic as well.
private static void InterruptAJob(JobKey foundJobKey, IScheduler sched)
{
if (null != foundJobKey)
{
sched.Interrupt(foundJobKey);
}
}
APPEND
Here is my code for finding a job key.
I would start with it..........put some breakpoints...and see if your JobKey is in the collection. Maybe modify the routine (after you figure out the magic-places) to find a job key by a certain criteria..and throw an exception if you don't find what you're expecting to find. Aka, don't "assume" the JobKey exists.....but query for it and throw the appropriate exception (or write the appropriate == null logic if no match is found).......vs the "assume it has to be there" approach.
Again, the below is "starter" code.......my Proof of Concept only had one job in it, so I didn't have to be specific.
private static JobKey FindaJobKey(IScheduler sched, ILogger logger)
{
JobKey returnJobKey = null;
IList<string> jobGroupNames = sched.GetJobGroupNames();
if (null != jobGroupNames)
{
if (jobGroupNames.Count > 0)
{
GroupMatcher<JobKey> groupMatcher = GroupMatcher<JobKey>.GroupEquals(jobGroupNames.FirstOrDefault());
Quartz.Collection.ISet<JobKey> keys = sched.GetJobKeys(groupMatcher);
returnJobKey = keys.FirstOrDefault();
if (null == returnJobKey)
{
throw new ArgumentOutOfRangeException("No JobKey Found");
}
}
}
Thread.Sleep(TimeSpan.FromSeconds(1));
return returnJobKey;
}
APPEND:
Maybe something like this:
private static JobKey FindJobKey(IScheduler sched, ILogger logger, string jobGroupName)
{
JobKey returnJobKey = null;
IList<string> jobGroupNames = sched.GetJobGroupNames();
if (null != jobGroupNames)
{
string matchingJobGroupName = jobGroupNames.Where(s => s.Equals(jobGroupName, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
if (null != matchingJobGroupName)
{
GroupMatcher<JobKey> groupMatcher = GroupMatcher<JobKey>.GroupEquals(matchingJobGroupName);
Quartz.Collection.ISet<JobKey> keys = sched.GetJobKeys(groupMatcher);
if (null != keys)
{
if (keys.Count > 0)
{
throw new ArgumentOutOfRangeException(string.Format("More than one JobKey Found. (JobGroupName='{0}')", jobGroupName));
}
returnJobKey = keys.FirstOrDefault();
if (null != returnJobKey)
{
throw new ArgumentOutOfRangeException(string.Format("No JobKey Found. (JobGroupName='{0}')", jobGroupName));
}
}
}
}
Thread.Sleep(TimeSpan.FromSeconds(1));
return returnJobKey;
}
Another quick and dirty "look at what you got going on" method.
private static void ShowJobs(IScheduler sched)
{
Console.WriteLine("");
Console.WriteLine("ShowJobs : Start");
GroupMatcher<JobKey> matcherAll = GroupMatcher<JobKey>.AnyGroup();
Quartz.Collection.ISet<JobKey> jobKeys = sched.GetJobKeys(matcherAll);
foreach (JobKey jk in jobKeys)
{
Console.WriteLine(string.Format("{0} : {1}", jk.Group, jk.Name));
}
Console.WriteLine("ShowJobs : End");
Console.WriteLine("");
}
APPEND:
Maybe come at it another way. I slightly adjusted one method. But added a new one.
ReportIInterruptableJobs
See what ReportIInterruptableJobs reports.
private static void ShowJobs(IScheduler sched, ILogger logger)
{
Console.WriteLine("");
Console.WriteLine("ShowJobs : Start");
GroupMatcher<JobKey> matcherAll = GroupMatcher<JobKey>.AnyGroup();
Quartz.Collection.ISet<JobKey> jobKeys = sched.GetJobKeys(matcherAll);
foreach (JobKey jk in jobKeys)
{
Console.WriteLine(string.Format("{0} : {1}", jk.Group, jk.Name));
IJobDetail jobData = sched.GetJobDetail(jk);
if (null != jobData)
{
Console.WriteLine(string.Format("{0}", jobData.JobType.AssemblyQualifiedName));
}
}
Console.WriteLine("ShowJobs : End");
Console.WriteLine("");
}
private static void ReportIInterruptableJobs()
{
Type typ = typeof(IInterruptableJob);
ICollection<Type> types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => typ.IsAssignableFrom(p)).ToList();
if (null != types)
{
foreach (Type t in types)
{
Console.WriteLine(string.Format("{0}", t.AssemblyQualifiedName));
}
}
}
Are you 100% positive that the JobKey for your ListenerJob is in fact Listeners.Realtime? Could there be a different job that uses that JobKey? Could Listeners.Realtime be being updated to a different value by the time you call Interrupt()? Quartz is definitely finding a job with that JobKey, because Quartz does not throw an exception if it doesn't find a job at all. The job it IS finding does not implement IInterruptableJob. I would double check the JobKey value for your ListenerJob by setting a breakpoint in the Execute(IJobExecutionContext context) method of ListenerJob and inspecting the context.JobDetail.Key. My money is on the JobKey being different. Also, it might be helpful for us to see what the code for your BaseJob is.
I'm having trouble to understand how I can get the details of a job with Quartz version 2.3.2.
Until now, we used Quartz v1.0.x for jobs and I have to upgrade it to the latest version.
This is how we used to get the details of a job:
JobDetail job = scheduler.GetJobDetail(task.Name, groupName);
With version 2.3.2, the method GetJobDetail() doesn't have a constructor that takes 2 parameter anymore... instead, it takes a JobKey parameter.
Unfortunately I couldn't find a way to get a single JobKey.
What I tried is the following:
string groupName = !string.IsNullOrEmpty(task.GroupNameExtension) ? task.GroupNameExtension : task.GroupName;
var jobkeys = quartzScheduler.GetJobKeys(GroupMatcher<JobKey>.GroupContains(groupName));
var jobkey = jobkeys.Single(x => x.Name == task.Name);
var jobDetail = quartzScheduler.GetJobDetail(jobkey);
Is this the correct way to implement it / get the jobKey? (will there always be only one jobkey on the line var jobkey = jobkey.Single(...)?
Is there really no way to get a JobDetail without getting all the JobKeys first?
Is this the way Quartz wants us to get the JobDetail or is there a better / simpler way?
Thanks in advance
You can just create a new job key (which is just a fancy storage for a job name and a group name)
new JobKey("jobName", "jobGroupName");
As long as your job name and job group name is the same with which you created your job, you will be able to get your job detail.
var jobDetail = quartzScheduler.GetJobDetail(new JobKey("jobName", "jobGroupName"));
personnally, i implement a static method in my job class to centralise the job key creation so i don't have many litterals all over the place :
public static JobKey GetJobKey(EntityServer server)
{
return new JobKey("AutoRestart" + server.Id, "AutoRestart");
}
Note that it is also true for the triggerKey
public static TriggerKey GetTriggerKey(EntityServer server)
{
return new TriggerKey("AutoRestart" + server.Id, "AutoRestart");
}
I am using Task Scheduler for scheduling my task in c# application. I think i got the basic understanding of this library.
But now i stuck in a place where i want to create a custom action which will execute on the set schedule.Like the built-in action i.e EmailAction ( Which will send mail on set schedule ), ShowMessageAction ( Which will show alert message on set schedule ), i want to create an action which will run my c# code and that code will save some data to my database.
What I tried yet is: I created a class CustomAction which inherits Action, like :
public class NewAction : Microsoft.Win32.TaskScheduler.Action
{
public override string Id
{
get
{
return base.Id;
}
set
{
base.Id = value;
}
}
public NewAction()
{
}
}
And here is my task scheduler code :
..
...
// Get the service on the local machine
using (TaskService ts = new TaskService())
{
// Create a new task definition and assign properties
TaskDefinition td = ts.NewTask();
td.RegistrationInfo.Description = "Does something";
// Create a trigger that will fire the task at this time every other day
TimeTrigger tt = new TimeTrigger();
tt.StartBoundary = DateTime.Today + TimeSpan.FromHours(19) + TimeSpan.FromMinutes(1);
tt.EndBoundary = DateTime.Today + TimeSpan.FromHours(19) + TimeSpan.FromMinutes(3);
tt.Repetition.Interval = TimeSpan.FromMinutes(1);
td.Triggers.Add(tt);
// Create an action that will launch Notepad whenever the trigger fires
td.Actions.Add(new NewAction()); <==========================
// Register the task in the root folder
ts.RootFolder.RegisterTaskDefinition(#"Test", td);
// Remove the task we just created
//ts.RootFolder.DeleteTask("Test");
}
...
....
On the line (pointed by arrow) i am getting the exception :
value does not fall within the expected range task scheduler
I am not sure what i am trying to achieve is even possible or not, if it is possible than please guid me on the correct direction?
According my understanding of your question. I had implemented same thing, but I had used "Quartz" Scheduler instead of "Task Scheduler". It is very easy to implement. May be you can also try with this.
To reference:
http://quartznet.sourceforge.net/tutorial/
Please correct me if I am wrong.
I posted this question on the microsoft forum but wanted to post it here for additional exposure. I have searched everywhere for an example of what I'm trying to do with no luck.
http://social.msdn.microsoft.com/Forums/en-US/wfprerelease/thread/8a424c0b-44f8-4670-8d64-9c7142117b55
Basically, I would like to use the WorkflowApplication along with the WorkflowIdentity and persistence store to accomplish durable delays. I use WorkflowApplication because we have hundreds of workflows that need to be run dynamically so the WorkflowServiceHost is not really feasible in our scenario.
I used the Microsoft provided example, AbsoluteDelay, as a starting point and it works perfectly. The problem comes when I try to pass in a WorkflowIdentity to the constructor of the WorkflowApplication.
Then, the WaitForEvents event on the InstanceStore does not fire because it is expecting the default Identity (id 1), not my new Identity (id 2). The line that does store.Execute creates a record in the IdentityOwnerTable with SurrogateIdentityId set to 1 instead of the id of my identity.
How can I tell the InstanceStore to use my new Identity and not the default one? It seems as if it is something that can be passed through in the CreateWorkflowOwnerCommand metadata.
// Create the InstanceStore
SqlWorkflowInstanceStore store = new SqlWorkflowInstanceStore(_workflowDB);
// A well known property that is needed by WorkflowApplication and the InstanceStore
private static readonly XName _workflowHostTypePropertyName = XNamespace.Get("urn:schemas-microsoft-com:System.Activities/4.0/properties").GetName("WorkflowHostType");
// Create the XName for the workflow using the workflow's unique name
XName wfHostTypeName = XName.Get(workflowName);
// Configure a Default Owner for the instance store so instances can be re-loaded from WorkflowApplication
private static InstanceHandle CreateInstanceStoreOwner(InstanceStore store, XName wfHostTypeName)
{
InstanceHandle ownerHandle = store.CreateInstanceHandle();
CreateWorkflowOwnerWithIdentityCommand ownerCommand = new CreateWorkflowOwnerWithIdentityCommand()
{ //changed from CreateWorkflowOwnerCommand (did not fix problem)
InstanceOwnerMetadata =
{
{ _workflowHostTypePropertyName, new InstanceValue(wfHostTypeName) },
// Something here to identify the WorkflowIdentity ??
}
};
store.DefaultInstanceOwner = store.Execute(ownerHandle, ownerCommand, TimeSpan.FromSeconds(30)).InstanceOwner;
return ownerHandle;
}
private static void WaitForRunnableInstance(InstanceStore store, InstanceHandle ownerHandle)
{
// Only fires when using default Identity
IEnumerable<InstancePersistenceEvent> events = store.WaitForEvents(ownerHandle, TimeSpan.MaxValue);
bool foundRunnable = false;
// Loop through the persistence events looking for the HasRunnableWorkflow event (in this sample, it corresponds with
// the workflow instance whose timer has expired and is ready to be resumed by the host).
foreach (InstancePersistenceEvent persistenceEvent in events)
{
if (persistenceEvent.Equals(HasRunnableWorkflowEvent.Value))
{
foundRunnable = true;
break;
}
}
if (!foundRunnable)
{
throw new ApplicationException("Unexpected: No runnable instances found in the instance store");
}
}