Scheduled Tasks
The ColdBox Scheduled Tasks offers a fresh, programmatic and human approach to scheduling tasks on your server and multi-server application
Last updated
The ColdBox Scheduled Tasks offers a fresh, programmatic and human approach to scheduling tasks on your server and multi-server application
Last updated
Scheduled tasks have always been a point of soreness for many developers in ANY language. Especially choosing where to place them for execution: should it be cron? windows task scheduler? ColdFusion engine? Jenkins, Gitlab? and the list goes on and on.
The ColdBox Scheduled Tasks offers a fresh, programmatic and human approach to scheduling tasks on your server and multi-server application. It allows you to define your tasks in a portable Scheduler we lovingly call the Scheduler.cfc
which not only can be used to define your tasks, but also monitor all of their life-cycles and metrics of tasks. Since ColdBox is also hierarchical, it allows for every single ColdBox Module to also define a Scheduler
and register their own tasks as well. This is a revolutionary approach to scheduling tasks in an HMVC application.
The ColdBox Scheduler is built on top of the core async package Scheduler.
Every ColdBox application has a global scheduler created for you by convention and registered with a WireBox ID of appScheduler@coldbox
. However, you can have complete control of the scheduler by creating the following file: config/Scheduler.cfc
. This is a simple CFC with a configure()
method where you will define your tasks and several life-cycle methods.
Every Scheduler can create life-cycle methods and monitor the scheduled tasks:
Method | Description |
| Called after the scheduler has registered all schedules |
| Called before the scheduler is going to be shutdown |
| Called whenever ANY task fails |
| Called whenever ANY task succeeds |
| Called before ANY task runs |
| Called after ANY task runs |
The following methods are used to impact the operation of all scheduled tasks managed by the scheduler:
Method | Description |
| Set the cachename to use for all registered tasks |
| Set the server fixation to use for all registered tasks |
| Set the timezone to use for all registered tasks |
| Override the executor generated for the scheduler |
By default, all tasks are fixed to use the template
cache when doing server fixation. You can override the cachename by a task by task basis or set the global default into the scheduler.
By default, all task run on each server/container they are registered with. However, you can also pin them on a specific server using server fixation via the onOneServer()
method of the individual scheduled task. However, you can also tell the scheduler to do this for ALL tasks it manages using the setServerFixation()
method.
By default, all tasks run under the system default timezone which usually is UTC. However, if you would like to change to a different execution timezone, then you can use the setTimeZone()
method and pass in a valid timezone string:
You can find all valid time zone Id's here: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/ZoneId.html
Remember that some timezones utilize daylight savings time. When daylight saving time changes occur, your scheduled task may run twice or even not run at all. For this reason, we recommend avoiding timezone scheduling when possible.
By default the scheduler will register a scheduled
executor with a default of 20 threads for you with a name of appScheduler@coldbox-scheduler.
If you want to add in your own executor as per your configurations, then just call the setExecutor()
method.
You can find how to work with executors in our executors section.
Every scheduler has the following injections available to you in the variables
scope
Object | Description |
| The ColdBox application mapping path |
| Async manager reference |
| CacheBox reference |
| The name of the cache for server fixation and more for all tasks |
| The ColdBox version you are running |
| ColdBox controller reference |
| Scheduled executor |
| A pre-configured log object |
| A boolean flag indicating if the scheduler has started or not |
| The boolean flag that indicates the default for server fixation for all tasks |
| The collection of registered tasks |
| Java based timezone object |
| ColdBox utility |
| WireBox reference |
All module schedulers will have the following extra automatic injections:
Property | Description |
| The module’s mapping |
| The module’s path on disk |
| The module’s settings structure |
Every scheduler has several useful ColdBox interaction methods you can use when registering your tasks callable methods.
Method | Description |
| Announce an interception |
| Render an external view |
| Get a cache from CacheBox |
| Get a ColdBox setting |
| Retrieve a environment variable only |
| Get a instance object from WireBox |
| Function to get access to the java system |
| Get a module config |
| Get a module setting |
| Get the ColdBox Renderer |
| Get an app Setting |
| Retrieve a Java System property or env value by name. It looks at properties first then environment variables |
| Retrieve a Java System property only |
| Render a layout |
| Resolve a directory to be either relative or absolute in your application |
| Resolve a file to be either relative or absolute in your application |
| Run a ColdBox Event |
| Run a ColdBox Route |
| Check if a setting exists |
| Set a setting |
| Render a view |
Every scheduler has several utility methods:
Method | Description |
| Get an ordered array of all the tasks registered in the scheduler |
| Get the task record structure by name:
|
| Builds out a struct report for all the registered tasks in this scheduler |
| Check if a scheduler has a task registered by name |
| Has the scheduler started already |
| Cancel a task and remove it from the scheduler |
| Startup the scheduler. This is called by ColdBox for you. No need to call it. |
| Shutdown the scheduler |
| Register a new task and return back to you the task so you can build it out. |
Ok, now that we have seen all the capabilities of the scheduler, let's dive deep into scheduling tasks with the task( name )
method.
Once you call on this method, the scheduler will create a ColdBoxScheduledTask
object for you, configure it, wire it, register it and return it to you.
You can find the API Docs for this object here: https://s3.amazonaws.com/apidocs.ortussolutions.com/coldbox/6.4.0/coldbox/system/web/tasks/ColdBoxScheduledTask.html
You register the callable event via the call()
method on the task object. You can register a closure/lambda or a invokable CFC. If you register an object, then we will call on the object's run()
method by default, but you can change it using the method
argument and call any public/remote method.
There are many many frequency methods in ColdBox scheduled tasks that will enable the tasks in specific intervals. Every time you see that an argument receives a timeUnit
the available options are:
Nanosecond(s)
Microsecond(s)
Millisecond(s) - DEFAULT
Second(s)
Minute(s)
Hour(s)
Day(s)
Hint : Please note you can use the singular or plural name of the time unit.
Ok, let's go over the frequency methods:
Frequency Method | Description |
| Run the task every custom period of execution |
| Run the task every custom period of execution but with NO overlaps |
| Run the task every minute from the time it get's scheduled |
| Run the task every hour from the time it get's scheduled |
| Set the period to be hourly at a specific minute mark and 00 seconds |
| Run the task every day at midnight |
| Run the task daily with a specific time in 24 hour format: HH:mm |
| Run the task every Sunday at midnight |
| Run the task weekly on the given day of the week and time |
| Run the task on the first day of every month at midnight |
| Run the task every month on a specific day and time |
| Run the task on the first Monday of every month |
| Run the task on the last business day of the month |
| Run the task on the first day of the year at midnight |
| Set the period to be weekly at a specific time at a specific day of the week |
| Run the task on Saturday and Sunday |
| Run the task only on weekdays at a specific time. |
| Only on Mondays |
| Only on Tuesdays |
| Only on Wednesdays |
| Only on Thursdays |
| Only on Fridays |
| Only on Saturdays |
| Only on Sundays |
All time
arguments are defaulted to midnight (00:00)
By default all tasks that have interval rates/periods that will execute on that interval schedule. However, what happens if a task takes longer to execute than the period? Well, by default the task will execute even if the previous one has not executed. If you want to prevent this behavior, then you can use the withNoOverlaps()
method and ColdBox will register the tasks with a fixed delay. Meaning the intervals do not start counting until the last task has finished executing.
Spaced delays are a feature of the Scheduled Executors. There is even a spacedDelay( delay, timeUnit )
method in the Task object.
Every task can also have an initial delay of first execution by using the delay()
method.
The delay
is numeric and the timeUnit
can be:
days
hours
minutes
seconds
milliseconds (default)
microseconds
nanoseconds
Please note that the delay
pushes the execution of the task into the future only for the first execution.
A part from registering tasks that have specific intervals/frequencies you can also register tasks that can be executed ONCE ONLY. These are great for warming up caches, registering yourself with control planes, setting up initial data collections and so much more.
Basically, you don't register a frequency just the callable event. Usually, you can also combine them with a delay of execution, if you need them to fire off after certain amount of time has passed.
We already saw that a scheduler has life-cycle methods, but a task can also have several useful life-cycle methods:
Method | Description |
| Store the closure to execute after the task executes
|
| Store the closure to execute before the task executes
|
| Store the closure to execute if there is a failure running the task
|
| Store the closure to execute if the task completes successfully
|
By default, all tasks will ask the scheduler for the timezone to run in. However, you can override it on a task-by-task basis using the setTimezone( timezone )
method:
You can find all valid time zone Id's here: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/ZoneId.html
Remember that some timezones utilize daylight savings time. When daylight saving time changes occur, your scheduled task may run twice or even not run at all. For this reason, we recommend avoiding timezone scheduling when possible.
There are many ways to constrain the execution of a task. However, you can register a when()
closure that will be executed at runtime and boolean evaluated. If true
, then the task can run, else it is disabled.
All scheduled tasks support the ability to seed in the start and end dates via our DSL:
startOn( date, time = "00:00" )
endOn( data, time = "00:00" )
This means that you can tell the scheduler when the task will become active on a specific data and time (using the scheduler's timezone), and when the task will become disabled.
If you are running a cluster of your application and you register tasks they will run at their schedule in EVERY server/container the application has been deployed to. This might not be a great idea if you want only ONE task to run no matter how many servers/containers you have deployed your application on. For this situation you can use the onOneServer()
method which tells ColdBox to ONLY run the task once on the first server that wins the race condition.
This feature ONLY works when you are using a distributed cache like redis, mongo, elastic, couchbase or a JDBC CacheBox provider in CacheBox.
By default this feature leverages the template
cache provider in CacheBox. However, you can change which cache provider will be used for storing the locking and tracking entries.
All ColdBox applications have a runnable environment stored in the environment
setting. You can use that to register a task with constraints of environment using the onEnvironment( environment )
method. This means that the task will ONLY run on those environments. The environment
argument can be a single string, a list of environments or an array of environments.
Every task is runnable from registration according to the frequency you set. However, you can manually disable a task using the disable()
method:
Once you are ready to enable the task, you can use the enable()
method:
Thanks to the inspiration of TestBox where you can mark a spec or test to be skipped from execution by prefixing it with the letter x
you can do the same for any task declaration. If they are prefixed with the letter x
they will be registered but disabled automatically for you.
All tasks keep track of themselves and have lovely metrics. You can use the getStats()
method to get a a snapshot structure
of the stats in time. Here is what you get in the stats structure:
Metric | Description |
| The timestamp of when the task was created in memory |
| The hostname of the machine this task is registered with |
| The last time the task ran |
| The last result the task callable produced |
| The ip address of the server this task is registered with |
| A boolean flag indicating if the task has NEVER been ran |
| When the task will run next |
| How many times the task has failed execution |
| How many times the task has run |
| How many times the task has run and succeeded |
We have created some useful methods that you can use when working with asynchronous tasks:
Method | Description |
| Send output to the error stream |
| Get the CacheBox provider assigned for server fixation |
| Get the name of the cache region to use for server fixation |
| Get the assigned running environments for the task |
| Get the boolean flag that indicates that this task runs on all or one server |
| Verifies if the task is assigned a scheduler or not |
| Verifies if the task has been disabled by bit |
| Verifies if the task has been constrained to run by server fixation, environments, weekends, weekdays, dayOfWeek, or dayOfMonth |
| Send output to the output stream |
| Set the cache name to use for server fixation |
| This kicks off the task into the scheduled executor manually. This method is called for you by the scheduler upon application startup or module loading. |
Every module in ColdBox also has a convention of config/Scheduler.cfc
that if detected will register that scheduler for you with a WireBox ID of cbScheduler@{moduleName}
. ColdBox will register the scheduler for you and also store it in the module's configuration struct with a key of scheduler
. ColdBox will also manage it's lifecycle and destroy it if the module is unloaded. All the rules for schedulers apply, happy scheduling!