0

For testing reasons I want to be able to adjust what time Quartz.Net currently thinks it is so I do not necessarily have to wait hours, days, or weeks in order to check that my code is working.

For this purpose I created the following simple function (it is in F# but could be easily be done in C# or another language) :

let SimulateTime = fun () ->
    currentTime <- DateTimeOffset.UtcNow
    timeDifferenceInSeconds <- (currentTime - lastCheckedTime).TotalSeconds
    simulatedTime <- simulatedTime.AddSeconds((timeDifferenceInSeconds *scaleTimeBy))
    lastCheckedTime <- currentTime
    simulatedTime

Where currentTime, lastCheckedTime, and simulatedTime would all be of type DateTimeOffset and both timeDifferenceInSeconds and scaleTimeBy are of type float.

I then change SystemTime.Now and SystemTime.UtcNow to use the above function as follows :

SystemTime.Now <- 
    Func<DateTimeOffset>(
        fun () -> SimulateTime())
SystemTime.UtcNow <- 
    Func<DateTimeOffset>(
        fun () -> SimulateTime())

Which was shown by Mark Seemann in a previous question of mine that can find here.

Now this mostly works except it seems like the longer function causes it to be off by a decently wide margin. What I mean by this is that all of my triggers will misfire. For example if I have a trigger set to occur every hour and set scaleTimeBy to 60.0 so that every second passed counts as a minute, it will never actually trigger on time. If I have a misfire policy, the trigger can then go off but the time it lists for when it activated will be as late as the half hour mark (so takes a full 30 seconds slower than what it should have been in this example).

However I can do this :

Console.WriteLine(SimulateTime())
Thread.Sleep(TimeSpan.FromSeconds(60.0))
Console.WriteLine(SimulateTime())

And the difference between the two times output to the screen in this example will be exactly an hour so the call doesn't seem like it should be adding as much of a time difference than it does.

Anyone have any advice on how to fix this issue or a better way of handling this problem?

Edit : So the C# version of the SimulateTime function would be something like this :

public DateTimeOffset SimulateTime() {
    currentTime = DateTimeOffset.UtcNow;
    double timeDifference = (currentTime - lastCheckedTime).TotalSeconds;
    simulatedTime = simulatedTime.AddSeconds(timeDifference * scaleTimeBy);
    lastCheckedTime = currentTime
    return simulatedTime;}

If that helps anyone with solving this problem.

Community
  • 1
  • 1
Sean
  • 83
  • 1
  • 7
  • Although I now nothing about Quartz.Net, this sounds like an issue with Quartz.Net, or the way you use it. I'd guess that there are far more C# developers familiar with this library than F# developers, so may have a better chance of getting an answer if you reproduce the issue in C#, and post the C# code here instead of F#... – Mark Seemann Apr 22 '15 at 05:34
  • Does the above help more? – Sean Apr 22 '15 at 14:31
  • Also decided to see if could figure things out by having the timeDifference get printed along with current time when the simulate time function is called. It seems like Quartz.net usually does like 5 calls at about the same time and then wait before doing another set of calls. When testing this wait time seems to alternate between roughly 20 seconds and roughly 0.001 seconds (the 30 seconds that mention in main problem seems like more of an unusual outlier though the 20 seconds still is pretty bad), Anyone have any idea why that is? – Sean Apr 22 '15 at 15:26
  • So digging through the source code for Quartz.net I have found a variable called idleWaitTime in SchedulerThread that I am guessing is responsible. Would anyone be capable of confirming or has in past dealt with it? – Sean Apr 22 '15 at 18:37
  • Yeah, it does seem like it has to do with idleWaitTime and this issue seems like it usually only comes up when dealing with servers. It is potentially configurable and I'm trying to figure out how to do that now. If anyone does know how to change it (preferably in a way where can dynamically change it based off ScaleTimeBy) let me know because it will save me some work. – Sean Apr 22 '15 at 21:44
  • So looks like what want to do for my use case would be to use DirectSchedulerFactory : http://quartz-scheduler.org/api/2.2.0/org/quartz/impl/DirectSchedulerFactory.html And trying to determine how to use that now. – Sean Apr 23 '15 at 13:49

1 Answers1

1

So this issue is misfires caused by the fact that Quartz.net will idle and wait when it thinks it doesn't have any triggers occurring any time soon to avoid making too many calls. By default it waits about 30 seconds give or take if it doesn't have any triggers occurring in the time span. The idleWaitTime variable is a Timespan set in the QuartzSchedulerThread. Now when checking for triggers that might occur soon it also uses the BatchTimeWIndow from QuartzSchedulerResources.

Both idleWaitTime and BatchTimeWindow can be set in configuration/properties files where they'd be called "org.quartz.scheduler.idleWaitTime" and "org.quartz.scheduler.batchTriggerAcquisitionFireAheadTimeWindow."

Based off what it is called in BatchTimeWindow I thought it was just a bit of look ahead for grabbing a variable (which would like since if I'm speeding things up, I'd want a small idleWaitTime but I would want it to look further ahead for triggers because the few seconds your waiting is actually minutes so will trigger sooner than it thinks), but the description of "org.quartz.scheduler.batchTriggerAcquisitionFireAheadTimeWindow" on pages going over configuration properties implies that it can cause things to fire early and be less accurate. So to start here is the code for just modifying idleWaitTime

let threadpool = Quartz.Simpl.SimpleThreadPool()
let jobstore = Quartz.Simpl.RAMJobStore()
let idleWaitTime = TimeSpan.FromSeconds(30.0/scaleTimeBy)
let dbfailureretryinverval = TimeSpan(int64 15000)
Quartz.Impl.DirectSchedulerFactory.Instance.CreateScheduler("TestScheduler","TestInstance",threadpool,jobstore,idleWaitTime,dbfailureretryinverval)
let scheduler = Quartz.Impl.DirectSchedulerFactory.Instance.GetScheduler("TestScheduler")

You can create a Scheduler that has the idleWaitTime you want by using the DirectSchedulerFactory which probably could use a little bit better documentation. It takes also a bunch of stuff you may or may not want to modify depending on what you are working on. For threadpool I just use Quartz.net's default SimpleThreadPool because I do not care about messing with the threading at this time and would not want to explain how you go about doing so unless that was the whole point of the question. Information on jobstores is available here. I am using RAMJobStore here because it is simpler than AdoJobStore but it shouldn't matter for this example. The dbfailureretryinterval is another value that don't care about for this example so I just looked up what it is set to by default. Its value should matter the least for this example because not connecting to a database. For idleWaitTime might want to do more tests to figure out what is a good value for it, but I chose to go with just scaling its default value of 30 seconds by scaleTimeBy since that is what I'm using to scale how fast things are going by. So this should make it so if I am having the program simulate time going by at a much faster rate, then it should only remain idle for smaller periods of time. One important thing to note is that when create a scheduler in this way, it is not returned as well so need to make a separate call to get the scheduler I just created. I have no idea why this is this way, I'm guessing that if you are creating several Schedulers and not necessarily using all of them it is better this way.

Now after all that you are likely to still get a bit of a misfire rate. While it is now idling for much smaller units of time (only a few seconds so potentially an acceptable margin depending on what your use case is), it still has the issue of it is only then checking to see if it has a coming trigger in the next few fractions of a second.

So lets see if adding time to BatchTimeWindow helps matters?

let threadpool = Quartz.Simpl.SimpleThreadPool()
let threadexecutor = Quartz.Impl.DefaultThreadExecutor()
let jobstore = Quartz.Simpl.RAMJobStore()
let schedulepluginmap = System.Collections.Generic.Dictionary<String,Quartz.Spi.ISchedulerPlugin>()
let idleWaitTime = TimeSpan.FromSeconds(30.0/timeScale)
let maxBatchSize = 1
let batchTimeWindow = TimeSpan.FromSeconds(timeScale)
let scheduleexporter = Quartz.Simpl.RemotingSchedulerExporter()
Quartz.Impl.DirectSchedulerFactory.Instance.CreateScheduler("TestScheduler","TestInstance",threadpool,threadexecutor,jobstore,schedulepluginmap,idleWaitTime,maxBatchSize,batchTimeWindow,scheduleexporter)
let scheduler = Quartz.Impl.DirectSchedulerFactory.Instance.GetScheduler("TestScheduler")

Now this has even more variables that don't really care about for the purposes of this example and won't even bother going over because adjusting batchTimeWindow actually makes it worse. Like getting you back to misfiring by 30 minutes. So no, batchTimeWindow while looks like might be useful is not. Only modify idleWaitTime.

Ideally for this use would want a small wait time and a larger look ahead time, but the option for that does not seem like its available.

Sean
  • 83
  • 1
  • 7
  • So this is what I've been able to figure out, I'm not marking it as my accepted answer yet in case something has something better. – Sean Apr 23 '15 at 16:21