Hey all!
Since this is something that comes up every now and then, I am creating this public post for describing the Long Polling/Continuous-Process Timer pattern, so that I can link to this thread instead of explaining it all over again. :-)
Imagine that we need to create a background process (queue service-likey) that runs constantly, 24/7, no interruptions. One way of achieving near-realtime (notice "near" in italics) data processing is by creating a Timer Action that does the processing, and then proceeds to call WakeTimer on itself right before ending the logic flow.
The problem is: the WakeTimer essentially just sets the NextRun parameter of that Timer to the current date/time, which is then left to be picked up by the Scheduler Service the next time it polls pending Timers, taking normally up to 20 seconds each run.
In some [very] specific cases, we might want the queue to be as "realtime" as computationally possible (instead of just "near-realtime"). Examples of such cases are:
This is where the Long Polling/Continuous-Process pattern comes into play: it's a way of building a Timer that is constantly running for a few minutes, up until before reaching its timeout limit (at which time it gracefully shuts down and wakes up again for a new round of continuous processing).
But be advised: you should not implement this pattern just because you can; improper usage can seriously degrade server performance, as well as consume all available Timer slots very quickly, so be mindful of that.
When to use it:
When NOT to use it:
Now that you know when to use and, most importantly, when NOT to use this pattern, let's see how the actual implementation goes.
You will need two basic things: a Server Action that detects whether a Timer is reaching its timeout (see Avoid long-running timers and batch jobs), and the Timer Action itself using the Long Polling/Continuous-Process pattern.
First, create a Server Action of Function type called CheckTimeout() with the following parameters:
In it, add an Aggregate called GetMetaCyclicJob using the entities Meta_Cyclic_Job and Cyclic_Job_Shared from (System), as follows:
TIP: You can use the ActionInfo Forge component to automagically grab the TimerName value instead of manually providing it as an input parameter (optional, but really handy).
Following the Aggregate, add an Assign node setting the following values:
If(GetMetaCyclicJob.List.Current.Meta_Cyclic_Job.Effective_Timeout = 0, GetMetaCyclicJob.List.Current.Meta_Cyclic_Job.Timeout, GetMetaCyclicJob.List.Current.Meta_Cyclic_Job.Effective_Timeout) * 60
DiffSeconds(GetMetaCyclicJob.List.Current.Cyclic_Job_Shared.Is_Running_Since, CurrDateTime())
TimeoutSeconds - SecondsElapsed < MarginSeconds
Here's the final look of the code:
And that's it! This function now dynamically tells you whether your running Timer is reaching its timeout, taking into account the provided margin (in seconds) at which the Timer should stop before timing out, and the effective timeout configuration set in Service Center.
The pattern follows some criteria:
Here's a depiction of the logic flow using this pattern:
TIP: Try processing your data in small batches. For example, instead of updating all records at once, try just grabbing the top 100 records each time and keep moving forward. The processing loop runs so frequently that it works best when it's given smaller chunks of data to process each time.
TIP²: Set the schedule in Service Center to run every 5 minutes. This ensures that the Timer will boot-up again in 5 minutes in case it fails and stops for any weird reason.
And that's all there is to it. This can be a really powerful pattern, just be aware of the consequences of misusing it. With great power comes great responsibility. Let me know if this helps anyone, I would be curious to know that. Any feedback would be appreciated.
Thanks!
Thanks. Very good and complete post.
Best Regards
Great post,
Good explanation on when (not) to use.
There´ s one part i did not understand.
You say the problem is that it could take 20 seconds for the scheduler to react to a WakeTimer, but I´ m not seeing how you avoid that, there is still a WakeTimer.
Dorine
Good question.
Because we need the Timer to stop every few minutes of continuous processing (in order to give the Scheduler Service some breathing room so that other timers also get a chance to run if they ever end up queued), we are not actually abolishing the 20-sec delay entirely.
But it happens WAY less frequently than in the simpler scenario of just processing data only once and then calling WakeTimer, without looping it.
I've used this in a number of occasions for different customers and in all cases it's proven to be irrelevant that every 15 mins the Timer stops for (at most) 20 seconds before booting up again.
Ah ok,
I tought the purpose was to reduce those 20 seconds to 0, so when I started reading, I tought your trick would be to have two timers running the same logic.
So there's about 10 - 20 seconds overlap, ensuring almost no discontinuing of processing (provided of course there's not too many other timers running on the same server)
Hi,
What would happen if the timer were still running and the next run is in 5 mins? Will the timer run twice at the same time?