Scheduling Tasks
The Human Way!
Brad Wood
@bdw429s
Scheduled Tasks
Scheduled Task Options Today
● Adobe Scheduled Tasks
(Uses Quartz engine)
● Lucee Scheduled Tasks
(Uses custom scheduler thread)
● Cron jobs/Jenkins jobs that hit URL
Completely external to the JVM
Scheduled Task Pain Points
● Limited scheduling capabilities
● LImited error handling
● Hard to cluster
● Each CF engine has different config
● External scheduling isn’t portable
● Can ‘t contribute run-time additions to scheduled tasks
● Usually based around public facing CFMs
● Not OO, not functional
What does the JDK Offer?
● The java.util.concurrent package!
● “Utility classes commonly useful in concurrent programming…
includes a few small standardized extensible frameworks… that
provide useful functionality and are otherwise tedious or difficult
to implement. “
● TimeUnit class for tracking seconds, minutes, hours, etc
● ThreadPoolExecutor and ScheduledThreadPoolExecutor
provide tunable, flexible thread pools.
ColdBox Scheduled Tasks
● NOT ACTUALLY COLDBOX SPECIFIC!! Also available in standalone
WireBox, LogBox, and CacheBox via the AsyncManager
● Define robust schedules via fluent DSL
● Uses Configuration as Code so no CF engine config and all contained in the
app
● Runs the same on all CF engines
● No external scheduling mechanism
● Built-in lifecycle methods and exception handling
● Built in clustering support to only run some tasks on one server
● Composable module-specific schedulers for drop-in HMVC functionality
ColdBox Scheduled Tasks
ColdBox Scheduled Tasks
Getting Started
ColdBox 6.2.0+
ColdBox Scheduler Convention
● Located in /config/Scheduler.cfc
● Our app templates are already updated to include it
● Works similar to Router.cfc-- automatic inheritance and
fluent DSL to call to configure the scheduler
ColdBox Scheduler Examples
component {
function configure() {
task( "my-task" )
.call( ()=log.info( 'Time keeps on ticking...' ) )
.everyMinute();
}
}
ColdBox Scheduler Examples
component {
function configure() {
task( "Clear Unregistered Users" )
.call( () => getInstance( "UserService" ).clearRecentUsers() )
.everyDayAt( "09:00" );
}
}
ColdBox Scheduler Examples
component {
function configure() {
task( "Hearbeat" )
.call( () => runEvent( "main.heartbeat" ) )
.every( 5, "minutes" )
.onFailure( ( task, exception ) => {
getInstance( "System" ).sendBadHeartbeat( exception );
} );
}
}
Scheduler Life-Cycle Methods
Scheduler Life-Cycle Methods
● onStartup() - Called after the scheduler has registered all schedules
● onShutdown() - Called before the scheduler is going to be shutdown
● onAnyTaskError(task,exception) - Called whenever ANY task fails
● onAnyTaskSuccess(task,result) - Called whenever ANY task succeeds
● beforeAnyTask(task) - Called before ANY task runs
● afterAnyTask(task,result) - Called after ANY task runs
Scheduler Life-Cycle Methods
component {
function configure() {
// Tasks here
}
function onStartup(){
log.info( 'Scheduler ready to rock and roll!' );
}
function onAnyTaskError( required task, required e ){
log.error( 'Task #task.getName()# has bombed!', e );
}
}
/config/Scheduler.cfc
Scheduler Configuration Methods
Scheduler Configuration Methods
● setCacheName( cacheName ) - Set the cachename to use for all
registered tasks
● setServerFixation( boolean ) - Set the server fixation to use for all
registered tasks
● setTimezone( timezone ) - Set the timezone to use for all registered tasks
● setExecutor( executor ) - Override the executor generated for the
scheduler
Scheduler Configuration Methods
component {
function configure() {
setCacheName( "Redis" );
setServerFixation( true );
setTimezone( "America/Chicago" );
}
}
/config/Scheduler.cfc
Scheduler Additional Methods
● getSetting()
● getInstance()
● runEvent()
● runRoute()
● view()
● layout()
● announce()
Task call() method
Task call() method
component {
function configure() {
// Lambda Syntax
task( "my-task" )
.call( () => getInstance( "myService" ).runcleanup() )
.everyHour();
}
}
/config/Scheduler.cfc
Task call() method
component {
function configure() {
// Closure Syntax
task( "my-task" )
.call( function(){
getInstance( "myService" ).runcleanup()
} )
.everyHourAt( 45 );
}
}
/config/Scheduler.cfc
Task call() method
component {
function configure() {
// Object with run() method
task( "my-task" )
.call( getInstance( "MyTask" ) )
.everyDay();
}
}
/config/Scheduler.cfc
Task call() method
component {
function configure() {
// Object with a custom method
task( "my-task" )
.call( getInstance( "CacheService" ), "reapCache" )
.everydayAt( "13:00" );
}
}
/config/Scheduler.cfc
Task Schedule Times
Scheduler TimeUnit
.every( period, timeunit )
.spacedDelay( spacedDelay, timeunit )
● days
● hours
● minutes
● seconds
● milliseconds (default)
● microseconds
● nanoseconds
Scheduler everyXXX() methods
.everydayAt( "13:00" )
.everyMinute()
.everyHour()
.everyHourAt( minutes )
.everyDay()
.everyDayAt( time )
.everyWeek()
.everyWeekOn( day, time )
.everyMonth()
.everyMonthOn( day, time )
Scheduler everyXXX() methods
.onFirstBusinessDayOfTheMonth( time )
.onLastBusinessDayOfTheMonth( time )
.everyYear()
.everyYearOn( month, day, time )
Scheduler everyXXX() methods
.onWeekends( time )
.onWeekdays( time )
.onMondays( time )
.onTuesdays( time )
.onWednesdays( time )
.onThursdays( time )
.onFridays( time )
.onSaturdays( time )
.onSundays( time )
Scheduler one-off tasks
component {
function configure() {
// Warm up caches 1 minute after app comes online
task( "build-up-cache" )
.call( () => getInstance( "DataServices" ).buildCache() )
.delay( 1, "minutes" );
}
}
/config/Scheduler.cfc
Task Life-Cycle Methods
Task Life-Cycle Methods
● after( target ) - Store the closure to execute after the task executes
● before( target ) - Store the closure to execute before the task executes
● onFailure( target ) - Store the closure to execute if there is a failure running
the task
● onSuccess( target ) - Store the closure to execute if the task completes
successfully
Task Life-Cycle Methods
task( "cool-task" )
.call( ()=>{} )
.before( function( task ) {
log.info( '#task.getName()# about to run!' );
} )
.after( function( task, results ){
log.info( '#task.getName()# has completed!' );
} )
.onFailure( function( task, exception ){
log.error( '#task.getName()# blew up!', exception );
} )
.onSuccess( function( task, results ){
log.info( '#task.getName()# has completed!' );
} );
/config/Scheduler.cfc
Task Constraints
Task Constraints - when()
component {
property name='drinkService' inject;
function configure() {
task( "remove-thirst" )
.call( () => drinkService.orderDrinks() )
.hourly()
.when( () => drinkService.isHappyHour() );
}
}
/config/Scheduler.cfc
Task Constraints - server fixation
component {
function configure() {
task( "my-task" )
.call( () => getInstance( "securityService" ).cleanOldUsers() )
.daily()
.onOneServer();
}
}
/config/Scheduler.cfc
Task Constraints - environment
component {
function configure() {
task( "my-task" )
.call( () => getInstance( "securityService" ).cleanOldUsers() )
.daily()
.onEnvironment( "staging,production" );
}
}
/config/Scheduler.cfc
Task Stats
Task Stats
● created - The timestamp of when the task was created in memory
● lastRun - The last time the task ran
● nextRun - When the task will run next
● totalFailures - How many times the task has failed execution
● totalRuns - How many times the task has run
● totalSuccess - How many times the task has run and succeeded
Task Stats
getInstance( 'appScheduler@coldbox' )
.getTaskRecord( 'testharness-Heartbeat' )
.task
.getStats()
Schedulers For Modules
Schedulers For Modules
● Every module can have its own scheduler!
● Injectable as cbScheduler@{moduleName}
● Lifecycle is tied to module load/unload
● Provides portable, drop in tasks
Schedulers For Modules - Example
task( "unleashsdk-refresh-features" )
.call( getInstance( "UnleashSDK@unleashsdk" ), "refreshFeatures" )
.every( variables.refreshInterval, "seconds" )
.before( function() {
if ( log.canDebug() ) {
log.debug( "Starting to fetch new features from Unleash" );
}
} )
.onSuccess( function( task, results ) {
if ( log.canInfo() ) {
log.info( "Successfully refreshed features", results );
}
} )
.onFailure( function( task, exception ) {
if ( log.canError() ) {
log.error( "Exception when running task [unleashsdk-refresh-features]:", exception );
}
} );
https://github.com/coldbox-modules/unleashsdk/blob/main/config/Scheduler.cfc
The End (Q & A)
https://coldbox.ortusbooks.com/digging-deeper/scheduled-tasks
Brad Wood
@bdw429s
brad@bradwood.com

Scheduling tasks the human way - Brad Wood - ITB2021

  • 1.
    Scheduling Tasks The HumanWay! Brad Wood @bdw429s
  • 2.
  • 3.
    Scheduled Task OptionsToday ● Adobe Scheduled Tasks (Uses Quartz engine) ● Lucee Scheduled Tasks (Uses custom scheduler thread) ● Cron jobs/Jenkins jobs that hit URL Completely external to the JVM
  • 4.
    Scheduled Task PainPoints ● Limited scheduling capabilities ● LImited error handling ● Hard to cluster ● Each CF engine has different config ● External scheduling isn’t portable ● Can ‘t contribute run-time additions to scheduled tasks ● Usually based around public facing CFMs ● Not OO, not functional
  • 5.
    What does theJDK Offer? ● The java.util.concurrent package! ● “Utility classes commonly useful in concurrent programming… includes a few small standardized extensible frameworks… that provide useful functionality and are otherwise tedious or difficult to implement. “ ● TimeUnit class for tracking seconds, minutes, hours, etc ● ThreadPoolExecutor and ScheduledThreadPoolExecutor provide tunable, flexible thread pools.
  • 6.
    ColdBox Scheduled Tasks ●NOT ACTUALLY COLDBOX SPECIFIC!! Also available in standalone WireBox, LogBox, and CacheBox via the AsyncManager ● Define robust schedules via fluent DSL ● Uses Configuration as Code so no CF engine config and all contained in the app ● Runs the same on all CF engines ● No external scheduling mechanism ● Built-in lifecycle methods and exception handling ● Built in clustering support to only run some tasks on one server ● Composable module-specific schedulers for drop-in HMVC functionality
  • 7.
  • 8.
  • 9.
  • 10.
    ColdBox Scheduler Convention ●Located in /config/Scheduler.cfc ● Our app templates are already updated to include it ● Works similar to Router.cfc-- automatic inheritance and fluent DSL to call to configure the scheduler
  • 11.
    ColdBox Scheduler Examples component{ function configure() { task( "my-task" ) .call( ()=log.info( 'Time keeps on ticking...' ) ) .everyMinute(); } }
  • 12.
    ColdBox Scheduler Examples component{ function configure() { task( "Clear Unregistered Users" ) .call( () => getInstance( "UserService" ).clearRecentUsers() ) .everyDayAt( "09:00" ); } }
  • 13.
    ColdBox Scheduler Examples component{ function configure() { task( "Hearbeat" ) .call( () => runEvent( "main.heartbeat" ) ) .every( 5, "minutes" ) .onFailure( ( task, exception ) => { getInstance( "System" ).sendBadHeartbeat( exception ); } ); } }
  • 14.
  • 15.
    Scheduler Life-Cycle Methods ●onStartup() - Called after the scheduler has registered all schedules ● onShutdown() - Called before the scheduler is going to be shutdown ● onAnyTaskError(task,exception) - Called whenever ANY task fails ● onAnyTaskSuccess(task,result) - Called whenever ANY task succeeds ● beforeAnyTask(task) - Called before ANY task runs ● afterAnyTask(task,result) - Called after ANY task runs
  • 16.
    Scheduler Life-Cycle Methods component{ function configure() { // Tasks here } function onStartup(){ log.info( 'Scheduler ready to rock and roll!' ); } function onAnyTaskError( required task, required e ){ log.error( 'Task #task.getName()# has bombed!', e ); } } /config/Scheduler.cfc
  • 17.
  • 18.
    Scheduler Configuration Methods ●setCacheName( cacheName ) - Set the cachename to use for all registered tasks ● setServerFixation( boolean ) - Set the server fixation to use for all registered tasks ● setTimezone( timezone ) - Set the timezone to use for all registered tasks ● setExecutor( executor ) - Override the executor generated for the scheduler
  • 19.
    Scheduler Configuration Methods component{ function configure() { setCacheName( "Redis" ); setServerFixation( true ); setTimezone( "America/Chicago" ); } } /config/Scheduler.cfc
  • 20.
    Scheduler Additional Methods ●getSetting() ● getInstance() ● runEvent() ● runRoute() ● view() ● layout() ● announce()
  • 21.
  • 22.
    Task call() method component{ function configure() { // Lambda Syntax task( "my-task" ) .call( () => getInstance( "myService" ).runcleanup() ) .everyHour(); } } /config/Scheduler.cfc
  • 23.
    Task call() method component{ function configure() { // Closure Syntax task( "my-task" ) .call( function(){ getInstance( "myService" ).runcleanup() } ) .everyHourAt( 45 ); } } /config/Scheduler.cfc
  • 24.
    Task call() method component{ function configure() { // Object with run() method task( "my-task" ) .call( getInstance( "MyTask" ) ) .everyDay(); } } /config/Scheduler.cfc
  • 25.
    Task call() method component{ function configure() { // Object with a custom method task( "my-task" ) .call( getInstance( "CacheService" ), "reapCache" ) .everydayAt( "13:00" ); } } /config/Scheduler.cfc
  • 26.
  • 27.
    Scheduler TimeUnit .every( period,timeunit ) .spacedDelay( spacedDelay, timeunit ) ● days ● hours ● minutes ● seconds ● milliseconds (default) ● microseconds ● nanoseconds
  • 28.
    Scheduler everyXXX() methods .everydayAt("13:00" ) .everyMinute() .everyHour() .everyHourAt( minutes ) .everyDay() .everyDayAt( time ) .everyWeek() .everyWeekOn( day, time ) .everyMonth() .everyMonthOn( day, time )
  • 29.
    Scheduler everyXXX() methods .onFirstBusinessDayOfTheMonth(time ) .onLastBusinessDayOfTheMonth( time ) .everyYear() .everyYearOn( month, day, time )
  • 30.
    Scheduler everyXXX() methods .onWeekends(time ) .onWeekdays( time ) .onMondays( time ) .onTuesdays( time ) .onWednesdays( time ) .onThursdays( time ) .onFridays( time ) .onSaturdays( time ) .onSundays( time )
  • 31.
    Scheduler one-off tasks component{ function configure() { // Warm up caches 1 minute after app comes online task( "build-up-cache" ) .call( () => getInstance( "DataServices" ).buildCache() ) .delay( 1, "minutes" ); } } /config/Scheduler.cfc
  • 32.
  • 33.
    Task Life-Cycle Methods ●after( target ) - Store the closure to execute after the task executes ● before( target ) - Store the closure to execute before the task executes ● onFailure( target ) - Store the closure to execute if there is a failure running the task ● onSuccess( target ) - Store the closure to execute if the task completes successfully
  • 34.
    Task Life-Cycle Methods task("cool-task" ) .call( ()=>{} ) .before( function( task ) { log.info( '#task.getName()# about to run!' ); } ) .after( function( task, results ){ log.info( '#task.getName()# has completed!' ); } ) .onFailure( function( task, exception ){ log.error( '#task.getName()# blew up!', exception ); } ) .onSuccess( function( task, results ){ log.info( '#task.getName()# has completed!' ); } ); /config/Scheduler.cfc
  • 35.
  • 36.
    Task Constraints -when() component { property name='drinkService' inject; function configure() { task( "remove-thirst" ) .call( () => drinkService.orderDrinks() ) .hourly() .when( () => drinkService.isHappyHour() ); } } /config/Scheduler.cfc
  • 37.
    Task Constraints -server fixation component { function configure() { task( "my-task" ) .call( () => getInstance( "securityService" ).cleanOldUsers() ) .daily() .onOneServer(); } } /config/Scheduler.cfc
  • 38.
    Task Constraints -environment component { function configure() { task( "my-task" ) .call( () => getInstance( "securityService" ).cleanOldUsers() ) .daily() .onEnvironment( "staging,production" ); } } /config/Scheduler.cfc
  • 39.
  • 40.
    Task Stats ● created- The timestamp of when the task was created in memory ● lastRun - The last time the task ran ● nextRun - When the task will run next ● totalFailures - How many times the task has failed execution ● totalRuns - How many times the task has run ● totalSuccess - How many times the task has run and succeeded
  • 41.
    Task Stats getInstance( 'appScheduler@coldbox') .getTaskRecord( 'testharness-Heartbeat' ) .task .getStats()
  • 42.
  • 43.
    Schedulers For Modules ●Every module can have its own scheduler! ● Injectable as cbScheduler@{moduleName} ● Lifecycle is tied to module load/unload ● Provides portable, drop in tasks
  • 44.
    Schedulers For Modules- Example task( "unleashsdk-refresh-features" ) .call( getInstance( "UnleashSDK@unleashsdk" ), "refreshFeatures" ) .every( variables.refreshInterval, "seconds" ) .before( function() { if ( log.canDebug() ) { log.debug( "Starting to fetch new features from Unleash" ); } } ) .onSuccess( function( task, results ) { if ( log.canInfo() ) { log.info( "Successfully refreshed features", results ); } } ) .onFailure( function( task, exception ) { if ( log.canError() ) { log.error( "Exception when running task [unleashsdk-refresh-features]:", exception ); } } ); https://github.com/coldbox-modules/unleashsdk/blob/main/config/Scheduler.cfc
  • 45.
    The End (Q& A) https://coldbox.ortusbooks.com/digging-deeper/scheduled-tasks Brad Wood @bdw429s brad@bradwood.com