# Scheduled Tasks

## 🎯 Introduction

The BoxLang async framework provides a powerful and flexible way to schedule tasks and workloads in your applications. Whether you need to run tasks at specific intervals, one-off tasks, or manage complex scheduling scenarios, the async package has you covered. It allows you to schedule tasks using a human-readable DSL (Domain Specific Language) that is both fluent and functional. This makes it easy to define when and how tasks should run, without getting bogged down in complex configurations.

You have three main approaches to scheduling tasks in BoxLang:

1. **📋 Scheduler Approach**: Create a scheduler and register tasks in it
2. **⚡ Scheduled Executor Approach**: Create a `ScheduledExecutor` and send task objects into it
3. **🖥️ CLI Runner Approach**: Use the `boxlang schedule {path.to.Scheduler.bx}` command to run tasks from the CLI

{% hint style="success" %}
With our scheduled tasks you can run either one-off tasks or periodically tasks.
{% endhint %}

The way to do executor tasks is documented in our [Executors Section](https://boxlang.ortusbooks.com/boxlang-framework/asynchronous-programming/executors), this guide focuses on the scheduler runtime and CLI runner approaches.

## 🏗️ Scheduler Service

BoxLang provides a `SchedulerService` that manages all the global, application, and module schedulers. There is really no need to interact with it, but if you want to you can access it via the `boxRuntime().getSchedulerService()` method. This service is responsible for managing all the schedulers in your application, including starting and stopping them, and providing access to the registered tasks.

## ⚙️ Runtime Configuration

The BoxLang configuration file located at `{BoxLangHome}/config/boxlang.json` contains all the necessary configurations and tunings for the scheduled tasks framework. Here are the main configurations you can set:

```json
// BoxLang Scheduler
// These are managed by the SchedulerService and registered upon startup
// or via a boxlang schedule [scheduler.bx] call
"scheduler": {
    // The default scheduler for all scheduled tasks
    // Each scheduler can have a different executor if needed
    "executor": "scheduled-tasks",
    // The cache to leverage for server fixation or distribution
    "cacheName": "default",
    // An array of BoxLang Schedulers to register upon startup
    // Must be an absolute path to the scheduler file
    // You can use the ${user-dir} or ${boxlang-home} variables or any other environment variable
    // Example: "schedulers": [ "/path/to/Scheduler.bx" ]
    "schedulers": [],
    // HTTP-driven tasks to register upon startup. This block is typically populated automatically by the bx:schedule component
    // BoxLang writes task definitions to the file configured by tasksFile (default: ${boxLangHome}/config/tasks.json)
    // and reloads them on startup.
    "tasksFile": "${boxlang-home}/config/tasks.json"
},
```

### Executor

The `executor` property defines the default executor to use for all scheduled tasks. This can be overridden on a per-scheduler basis. The default executor is `scheduled-tasks`, which is a `ScheduledExecutor` with a default of 20 threads, which can be found in the `executors` section.

### Cache Name

The `cacheName` property defines the cache to use for server fixation or distribution. This is useful if you want to share scheduled tasks across multiple servers in a cluster. The default cache is `default`, which is the default cache defined in the BoxLang configuration. This can be found in the `caches` section of the configuration file and can be overridden on a per-scheduler basis.

### Schedulers

The `schedulers` property is an array of BoxLang schedulers to register upon startup. Each scheduler is defined by an absolute path to the scheduler class (e.g. `/path/to/Scheduler.bx`). You can use the `${user-dir}` or `${boxlang-home}` variables or any other environment variable to define the path. This allows you to define multiple schedulers that can be registered and managed by the `SchedulerService` at runtime startup.

### TasksFile

The `tasksFile` property defines the file where HTTP-driven tasks are registered. This is typically managed by the `bx:schedule` component, which writes task definitions to this file and reloads them on startup. By default, this file is located at `${boxLangHome}/config/tasks.json`. This file contains the definitions of tasks that are created and managed via the `bx:schedule` component, which allows you to create and manage scheduled tasks at runtime without needing to define them in a scheduler class.

See the [bx:schedule component guide](https://boxlang.ortusbooks.com/boxlang-framework/asynchronous-programming/scheduling-component) for the recommended way to create and manage these tasks at runtime.

### bx:schedule Component

For HTTP-driven scheduled tasks, BoxLang provides the `bx:schedule` component (also available as `cfschedule` for CFML compatibility). This tag/script API lets you create, update, delete, pause, resume, and list scheduled tasks without writing a Scheduler class. Tasks created this way persist automatically to disk and survive runtime restarts.

```javascript
// Create a task that runs every night at 2 AM
bx:schedule action="create"
    task="nightlyCleanup"
    url="https://myapp.com/tasks/cleanup"
    cronTime="0 2 * * *";
```

See the full [Schedule Component Guide](https://boxlang.ortusbooks.com/boxlang-framework/asynchronous-programming/scheduling-component) and the [bx:schedule reference](https://boxlang.ortusbooks.com/boxlang-language/reference/components/async/schedule) for all available attributes and actions.

## ⏳ Schedulers

A `Scheduler` is a self-contained class that can track multiple scheduled tasks for you and give you enhanced and fluent approaches to scheduling. It is a powerful tool that allows you to register tasks, configure them, and manage their execution. Each scheduler class inherits dynamically from the `BaseScheduler` Java class, giving you access to all of its powerful methods and capabilities.

You can find the API Docs here: <https://s3.amazonaws.com/apidocs.ortussolutions.com/boxlang/1.3.0/ortus/boxlang/runtime/async/tasks/BaseScheduler.html>

Let's review the structure of a BoxLang scheduler class:

```javascript
class {

	// Properties - These are automatically injected by the BoxLang runtime
	property name="scheduler";       // The BaseScheduler instance this class wraps
	property name="runtime";         // The BoxRuntime instance
	property name="logger";          // A logger instance for this scheduler
	property name="asyncService";    // The AsyncService for executor management
	property name="cacheService";    // The CacheService for distributed scheduling
	property name="interceptorService"; // The InterceptorService for event broadcasting

	/**
	 * The configure method is called by the BoxLang runtime to allow the scheduler to configure itself.
	 *
	 * This is where you define your tasks and setup global configuration.
	 */
	function configure(){
		// Setup Scheduler Properties
		scheduler.setSchedulerName( "My-Scheduler" );
		scheduler.setTimezone( "UTC" );

		// Define a lambda task
		scheduler.task( "My test Task" )
			.call( () -> {
				println( "I am a lambda task: #dateFormat( now(), "full" )#" );
			} )
			.every( 2, "second" );
	}

	/**
	 * --------------------------------------------------------------------------
	 * Life - Cycle Callbacks
	 * --------------------------------------------------------------------------
	 */

	/**
	 * Called after the scheduler has registered all schedules
	 */
	void function onStartup(){
		println( "I have started!" & scheduler.getSchedulerName() );
	}

	/**
	 * Called before the scheduler is going to be shutdown
	 */
	void function onShutdown(){
		println( "I have shutdown!" & scheduler.getSchedulerName() );
	}

	/**
	 * Called whenever ANY task fails
	 *
	 * @task      The task that got executed
	 * @exception The exception object
	 */
	function onAnyTaskError( task, exception ){
		println( "Any task [#task.getName()#] blew up " & exception.getMessage() );
	}

	/**
	 * Called whenever ANY task succeeds
	 *
	 * @task   The task that got executed
	 * @result The result (if any) that the task produced as an Optional
	 */
	function onAnyTaskSuccess( task, result ){
		println( "on any task success [#task.getName()#]" );
		println( "results for task are: " & result.orElse( "No result" ) );
	}

	/**
	 * Called before ANY task runs
	 *
	 * @task The task about to be executed
	 */
	function beforeAnyTask( task ){
		println( "before any task [#task.getName()#]" );
	}

	/**
	 * Called after ANY task runs
	 *
	 * @task   The task that got executed
	 * @result The result (if any) that the task produced as an Optional
	 */
	function afterAnyTask( task, result ){
		println( "after any task completed [#task.getName()#]" );
		println( "results for task are: " & result.orElse( "No result" ) );
	}

}
```

### 🕹️Properties

The scheduler properties are automatically injected by the BoxLang runtime and provide access to various services:

| Property             | Description                                                                                   |
| -------------------- | --------------------------------------------------------------------------------------------- |
| `scheduler`          | The `BaseScheduler` instance that your class wraps, providing access to all scheduler methods |
| `runtime`            | The `BoxRuntime` instance for access to global services                                       |
| `logger`             | A logger instance specifically configured for this scheduler                                  |
| `asyncService`       | The `AsyncService` for managing executors and async operations                                |
| `cacheService`       | The `CacheService` for distributed scheduling and state management                            |
| `interceptorService` | The `InterceptorService` for broadcasting events and interceptors                             |

### 🔧 Configuration Methods

Your scheduler class has access to all the methods from the `BaseScheduler` class through the `scheduler` property:

| Method                       | Description                                                           |
| ---------------------------- | --------------------------------------------------------------------- |
| `getRegisteredTasks()`       | Get a list of all registered task names                               |
| `getTaskRecord( name )`      | Get the task record for a specific task                               |
| `getTaskStats()`             | Get statistics for all tasks                                          |
| `hasTask( name )`            | Check if a task is registered                                         |
| `removeTask( name )`         | Remove a task from the scheduler                                      |
| `restart()`                  | Restart the scheduler                                                 |
| `restart( force, timeout )`  | Restart the scheduler with force flag and custom timeout              |
| `clearTasks()`               | Clear all tasks from the scheduler (usually done by restart)          |
| `startupTask( task )`        | Manually startup a specific task                                      |
| `startupTask( taskName )`    | Manually startup a task by name                                       |
| `hasStarted()`               | Check if the scheduler has been started                               |
| `isRunning()`                | Alias for `hasStarted()` - check if scheduler is running              |
| `getStartedAt()`             | Get the timestamp when the scheduler was started                      |
| `setContext( context )`      | Set the BoxLang context for task execution                            |
| `getContext()`               | Get the current BoxLang context                                       |
| `setSchedulerName( name )`   | Set the human-readable name for this scheduler                        |
| `setTimezone( timezone )`    | Set the timezone for all tasks (default: system timezone)             |
| `setDefaultTimezone()`       | Set the timezone to system default                                    |
| `getAsyncService()`          | Get the async service bound to this scheduler                         |
| `setExecutor( executor )`    | Set the executor record for this scheduler                            |
| `getExecutor()`              | Get the executor record                                               |
| `getLogger()`                | Get the logger instance for this scheduler                            |
| `shutdown()`                 | Shutdown the scheduler gracefully                                     |
| `shutdown( force )`          | Shutdown with force flag                                              |
| `shutdown( force, timeout )` | Shutdown with force flag and timeout                                  |
| `startup()`                  | Start the scheduler and all its tasks                                 |
| `task( name )`               | Register a new task with the given name                               |
| `task( name, group )`        | Register a new task with name and group                               |
| `xtask( name )`              | Register a new task but disable it immediately (useful for debugging) |
| `xtask( name, group )`       | Register a disabled task with name and group                          |

{% hint style="warning" %}
We always recommend you give a scheduler a name and a timezone.
{% endhint %}

### 🌍 Timezone

By default, all tasks will ask the scheduler for the timezone to run in, which most likely will be the runtime timezone. However, you can override it on a task-by-task basis using the `setTimezone( timezone )` method:

```javascript
// Pass a valid Timezone string that can be parsed into a ZoneId
setTimezone( "America/Chicago" )
```

{% hint style="success" %}
You can find all valid time zone Id's here: <https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/time/ZoneId.html>
{% endhint %}

{% hint style="warning" %}
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.
{% endhint %}

### 🚀 Scheduling Tasks

Now that we have seen the capabilities of the scheduler, let's dive deep into scheduling tasks with the `task( name )` method.

#### 📝 Registering Tasks

Once you call this method, the scheduler will create a `ScheduledTask` object for you, configure it, and register it. The task object provides a fluent API for configuring when and how the task should run.

```javascript
task( "my-task" )
```

#### 🎯 Task Closure/Lambda/Object

You register the callable event via the `call()` method on the task object. You can register a closure/lambda or an object. If you register an object, then we will call the object's `run()` method by default, but you can change it using the `method` argument and call any public method.

```javascript
// Lambda Syntax
task( "my-task" )
    .call( () => runCleanup() )
    .everyHour();

// Closure Syntax
task( "my-task" )
    .call( function(){
        // task here
        println( "Task executed at: " & now() );
    } )
    .everyHourAt( 45 );

// Object with run() method
task( "my-task" )
    .call( createObject( "MyTaskObject" ) )
    .everyDay()

// Object with a custom method
task( "my-task" )
    .call( createObject( "MyTaskObject" ), "reapCache" )
    .everydayAt( "13:00" )
```

#### 📛 Task Names and Groups

Always provide meaningful and unique task names as they serve as the primary identifier for your tasks. Task names are used for:

* **Logging and debugging** - All log entries reference the task by name
* **Statistics and monitoring** - Task metrics are tracked by name
* **Management operations** - Starting, stopping, and retrieving tasks by name
* **Error reporting** - Exception messages include the task name for identification

**Groups provide additional organization benefits:**

* **Logical organization** - Group related tasks together (e.g., "maintenance", "reports", "monitoring")
* **Bulk operations** - Manage multiple tasks as a group
* **Statistics aggregation** - View metrics by task group
* **Easier maintenance** - Quickly identify and manage task categories

```javascript
// ✅ Good: Descriptive names and logical grouping
scheduler.task( "cleanup-expired-sessions", "maintenance" )
scheduler.task( "generate-daily-report", "reports" )
scheduler.task( "health-check-database", "monitoring" )

// or using chaining
scheduler.task( "cleanup" ).setGroup( "maintenance" )
scheduler.task( "health-check-database" ).setGroup( "monitoring" )

// ❌ Bad: Generic names without context
scheduler.task( "task1" )
scheduler.task( "job" )
scheduler.task( "process" )
```

#### ⏰ Frequencies

There are many many frequency methods in scheduled tasks that will enable the tasks in specific intervals. Every time you see that an argument receives a `timeUnit` the available options are:

* days
* hours
* minutes
* seconds
* **milliseconds (default)**
* microseconds
* nanoseconds

Ok, let's go over the frequency methods:

| Frequency Method                       | Description                                                                  |
| -------------------------------------- | ---------------------------------------------------------------------------- |
| `every( period, timeunit )`            | Run the task every custom period of execution                                |
| `cron( expression )`                   | Schedule the task using a cron expression (5-field Unix or 6-field Quartz)   |
| `spacedDelay( spacedDelay, timeunit )` | Run the task every custom period of execution but with NO overlaps           |
| `everySecond()`                        | Run the task every second from the time it gets scheduled                    |
| `everyMinute()`                        | Run the task every minute from the time it get's scheduled                   |
| `everyHour()`                          | Run the task every hour from the time it get's scheduled                     |
| `everyHourAt( minutes )`               | Set the period to be hourly at a specific minute mark and 00 seconds         |
| `everyDay()`                           | Run the task every day at midnight                                           |
| `everyDayAt( time )`                   | Run the task daily with a specific time in 24 hour format: HH:mm             |
| `everyWeek()`                          | Run the task every Sunday at midnight                                        |
| `everyWeekOn( day, time )`             | Run the task weekly on the given day of the week and time                    |
| `everyMonth()`                         | Run the task on the first day of every month at midnight                     |
| `everyMonthOn( day, time )`            | Run the task every month on a specific day and time                          |
| `onFirstBusinessDayOfTheMonth( time )` | Run the task on the first Monday of every month                              |
| `onLastBusinessDayOfTheMonth( time )`  | Run the task on the last business day of the month                           |
| `everyYear()`                          | Run the task on the first day of the year at midnight                        |
| `everyYearOn( month, day, time )`      | Set the period to be weekly at a specific time at a specific day of the week |
| `onWeekends( time )`                   | Run the task on Saturday and Sunday                                          |
| `onWeekdays( time )`                   | Run the task only on weekdays at a specific time.                            |
| `onMondays( time )`                    | Only on Mondays                                                              |
| `onTuesdays( time )`                   | Only on Tuesdays                                                             |
| `onWednesdays( time )`                 | Only on Wednesdays                                                           |
| `onThursdays( time )`                  | Only on Thursdays                                                            |
| `onFridays( time )`                    | Only on Fridays                                                              |
| `onSaturdays( time )`                  | Only on Saturdays                                                            |
| `onSundays( time )`                    | Only on Sundays                                                              |

{% hint style="success" %}
All `time` arguments are defaulted to midnight (00:00)
{% endhint %}

#### 🗓️ Cron Expression Scheduling

Use `.cron( expression )` to schedule a task using a standard cron expression instead of the named frequency methods. Both 5-field Unix and 6-field Quartz formats are supported.

```javascript
// 5-field Unix: minute hour day month weekday
// Run at 2:30 AM every day
task( "nightly-report" )
    .call( () => generateReport() )
    .cron( "30 2 * * *" );

// 6-field Quartz: second minute hour day month weekday
// Run at noon on the first day of every month
task( "monthly-summary" )
    .call( () => generateMonthlySummary() )
    .cron( "0 0 12 1 * ?" );
```

#### ⏱️ Time Unit Methods

You can also use fluent time unit methods to set the time unit for your periods when using the `every()` method:

| Time Unit Method   | Description                       |
| ------------------ | --------------------------------- |
| `inDays()`         | Set the time unit to days         |
| `inHours()`        | Set the time unit to hours        |
| `inMinutes()`      | Set the time unit to minutes      |
| `inSeconds()`      | Set the time unit to seconds      |
| `inMilliseconds()` | Set the time unit to milliseconds |
| `inMicroseconds()` | Set the time unit to microseconds |
| `inNanoseconds()`  | Set the time unit to nanoseconds  |

**Usage Examples:**

```javascript
// Using time unit methods with every()
task( "data-sync" )
    .call( () => syncData() )
    .every( 30 )
    .inMinutes();

// Equivalent to
task( "data-sync" )
    .call( () => syncData() )
    .every( 30, "minutes" );

// Complex example with chaining
task( "cache-warmup" )
    .call( () => warmupCache() )
    .every( 2 )
    .inHours()
    .startOnTime( "06:00" )
    .endOnTime( "22:00" );
```

#### 🚫 Preventing Overlaps / Stacking

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 not execute if the previous one has not finished executing, causing the pending task to execute immediately after the current one completes ( Stacking Tasks ). If you want to prevent this behavior, then you can use the `withNoOverlaps()` method and BoxLang will register the tasks with a *fixed delay*. Meaning the intervals do not start counting until the last task has finished executing.

```javascript
task( "test" )
	.call( () => createObject( "CacheService" ).reap() )
	.everyMinute()
	.withNoOverlaps();
```

{% hint style="success" %}
Spaced delays are a feature of the Scheduled Executors. There is even a `spacedDelay( delay, timeUnit )` method in the Task object.
{% endhint %}

#### ⏳ Delaying First Execution

Every task can also have an initial delay of first execution by using the `delay()` method.

```javascript
/**
 * Set a delay in the running of the task that will be registered with this schedule
 *
 * @delay The delay that will be used before executing the task
 * @timeUnit The time unit to use, available units are: days, hours, microseconds, milliseconds, minutes, nanoseconds, and seconds. The default is milliseconds
 */
ScheduledTask function delay( numeric delay, timeUnit = "milliseconds" )
```

The `delay` is numeric and the `timeUnit` can be:

* days
* hours
* minutes
* seconds
* **milliseconds (default)**
* microseconds
* nanoseconds

```javascript
// Lambda Syntax
task( "my-task" )
    .call( () => wirebox.getInstance( "MyObject" ).runcleanup() )
    .delay( "5000" )
    .everyHour();
```

{% hint style="info" %}
Please note that the `delay` pushes the execution of the task into the future only for the first execution.
{% endhint %}

#### 🎯 One Off Tasks

Apart 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.

```javascript
task( "build-up-cache" )
    .call( () => createObject( "MyService" ).buildCache() )
    .delay( 1, "minutes" );

task( "notify-admin-server-is-up" )
    .call( () => createObject( "NotificationService" ).notifyAppIsUp( getServerIP() ) )
    .delay( 30, "seconds" );

task( "register-container" )
    .call( () => createObject( "RegistrationService" ).register() )
    .delay( 30, "seconds" );
```

#### 🔄 Life-Cycle Methods

We already saw that a scheduler has life-cycle methods, but a task can also have several useful life-cycle methods:

| Method                | Description                                                                                        |
| --------------------- | -------------------------------------------------------------------------------------------------- |
| `after( target )`     | Store the closure to execute after the task executes: `function( task, results )`                  |
| `before( target )`    | Store the closure to execute before the task executes: `function( task )`                          |
| `onFailure( target )` | Store the closure to execute if there is a failure running the task: `function( task, exception )` |
| `onSuccess( target )` | Store the closure to execute if the task completes successfully: `function( task, results )`       |

```javascript
task( "testharness-Heartbeat" )
	.call( () => {
		if ( randRange(1, 5) == 1 ){
			throw( message = "I am throwing up randomly!", type="RandomThrowup" );
		}
		println( "====> I am in a test harness test schedule!" );
	} )
	.every( "5", "seconds" )
	.before( ( task )  => {
		println( "====> Running before the task!" );
	} )
	.after( ( task, results ) => {
		println( "====> Running after the task!" );
	} )
	.onFailure( ( task, exception ) => {
		println( "====> test schedule just failed!! #exception.message#" );
	} )
	.onSuccess( ( task, results ) => {
		println( "====> Test scheduler success : Stats: #task.getStats().toString()#" );
	} );
```

#### ✅ Truth Test Constraints

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.

```javascript
task( "my-task" )
    .call( () => createObject( "UserService" ).cleanOldUsers() )
    .daily()
    .when( () => {
        // Can we run this task?
        return true;
    } );
```

#### 📅 Start and End Dates

All scheduled tasks support the ability to seed in the **startOnDateTime** and **endOnDateTime** dates via our DSL:

* `startOn( date, time = "00:00" )`
* `endOn( date, time = "00:00" )`

This means that you can tell the scheduler when the task will become active on a specific date and time (using the scheduler's timezone), and when the task will become disabled.

```javascript
task( "restricted-task" )
  .call( () => createObject( "MaintenanceService" ).performMaintenance() )
  .everyHour()
  .startOn( "2022-01-01", "00:00" )
  .endOn( "2022-04-01" )
```

#### 🕐 Start and End Times

All scheduled tasks support the ability to seed in the **startTime** and **endTime** dates via our DSL:

* `startOnTime( time = "00:00" )`
* `endOnTime( time = "00:00" )`
* `between( startTime = "00:00", endTime "00:00" )`

This means that you can tell the scheduler to restrict the execution of the task after and/or before a certain time (using the scheduler's timezone).

```javascript
task( "restricted-task" )
  .call( () => createObject( "ReportService" ).generateReport() )
  .everyMinute()
  .between( "09:00", "17:00" )
```

#### ⏸️ Disabling/Pausing Tasks

Every task is runnable from registration according to the frequency you set. However, you can manually disable a task using the `disable()` method:

```javascript
task( "my-task" )
    .call( () => createObject( "SecurityService" ).cleanOldUsers() )
    .daily()
    .disable();
```

Once you are ready to enable the task, you can use the `enable()` method:

```javascript
myTask.enable()
```

{% hint style="warning" %}
Registering a task as disabled can lead to a task continuing to execute if it was later enabled and then removed via `removeTask( name )` and not disabled again before doing so.
{% endhint %}

#### 📊 Task Stats

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                                                      |
| ------------------- | ---------------------------------------------------------------- |
| `created`           | The timestamp of when the task was created in memory             |
| `group`             | The name of the task group if any, this can be empty             |
| `inetHost`          | The hostname of the machine this task is registered with         |
| `lastRun`           | The last time the task ran, `null` by default                    |
| `lastResult`        | The last result the task callable produced. This is an `Attempt` |
| `lastExecutionTime` | How long the last execution took in milliseconds                 |
| `localIp`           | The ip address of the server this task is registered with        |
| `name`              | The name of the task                                             |
| `neverRun`          | A boolean flag indicating if the task has NEVER been ran         |
| `nextRun`           | When the task will run next, `null` by default                   |
| `totalFailures`     | How many times the task has failed execution, 0 by default       |
| `totalRuns`         | How many times the task has run, 0 by default                    |
| `totalSuccess`      | How many times the task has run and succeeded, 0 by default      |

```javascript
/**
 * Called after ANY task runs
 *
 * @task The task that got executed
 * @result The result (if any) that the task produced
 */
function afterAnyTask( required task, result ){
	logger.info( "task #task.getName()# just ran. Metrics: #task.getStats().toString()#" );
}
```

#### 🛠️ Task Helpers

We have created some useful methods that you can use when working with asynchronous tasks:

| Method                     | Description                                                                                                                                                                          |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `hasScheduler()`           | Verifies if the task is assigned a scheduler or not                                                                                                                                  |
| `isAnnually()`             | If the task is scheduled annually                                                                                                                                                    |
| `isDisabled()`             | Verifies if the task has been disabled by bit                                                                                                                                        |
| `isEnabled()`              | Verifies if the task has been enabled (opposite of `isDisabled()`)                                                                                                                   |
| `isConstrained()`          | Verifies if the task has been constrained to run by dayOfMonth, dayOfWeek, firstBusinessDay, lastBusinessDay, weekdays, weekends, startOnDateTime, endOnDateTime, startTime, endTime |
| `isScheduled()`            | Verifies if the task has been scheduled for execution                                                                                                                                |
| `isNoOverlaps()`           | Verifies if the task has been configured to prevent overlapping executions                                                                                                           |
| `start()`                  | 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.                             |
| `enable()`                 | Enable the task for execution (sets disabled flag to false)                                                                                                                          |
| `disable()`                | Disable the task from execution (sets disabled flag to true)                                                                                                                         |
| `run( force )`             | Execute the task manually with optional force flag to bypass constraints                                                                                                             |
| `checkInterrupted()`       | Call periodically in long-running tasks to check if thread has been interrupted                                                                                                      |
| `cleanupTaskRun()`         | Internal cleanup method called after every task execution                                                                                                                            |
| `getNow()`                 | Get the current date/time in the task's configured timezone                                                                                                                          |
| `getLastResult()`          | Get the last result of the task execution as an Optional                                                                                                                             |
| `setMeta( struct )`        | Set the meta struct of the task. This is a placeholder for any data you want to be made available to you when working with a task                                                    |
| `setMetaKey( key, value )` | Set a key on the custom meta struct.                                                                                                                                                 |
| `deleteMetaKey( key )`     | Delete a key from the custom meta struct.                                                                                                                                            |
| `getMeta()`                | Get the complete meta struct for the task                                                                                                                                            |
| `setTimezone( timezone )`  | Set the timezone for the task (accepts string or ZoneId)                                                                                                                             |
| `getTimezone()`            | Get the timezone configured for the task                                                                                                                                             |
| `setName( name )`          | Set the human-readable name of the task                                                                                                                                              |
| `getName()`                | Get the human-readable name of the task                                                                                                                                              |
| `setGroup( group )`        | Set the group name for the task for logical organization                                                                                                                             |
| `getGroup()`               | Get the group name for the task                                                                                                                                                      |
| `getStats()`               | Get the statistics struct for the task containing execution metrics                                                                                                                  |

### 🌐 Global Schedulers

The global schedulers are the default schedulers that are registered upon startup. These are defined in the `schedulers` property of the configuration file we have seen above. You can define multiple global schedulers that can be used throughout your application.

### 📱 Per-Application Schedulers

The per-application schedulers are the schedulers that are registered for a specific application using the `Application.bx`. Just use the `this.schedulers` property to define the schedulers you want to register for your application. This is useful if you want to have different schedulers for different applications in your BoxLang environment.

```javascript
class{

    this.schedulers = [
        "config/YourAppScheduler.bx",
        "/myMapping/YourAppScheduler.bx"
    ]

}
```

Please note that you do not need to register absolute paths for schedulers in your application, you can use relative paths or even per-app mappings. The `SchedulerService` will automatically resolve the paths for you. Once your application starts, the `SchedulerService` will register all the schedulers defined in the `this.schedulers` property. Once the application stops, the `SchedulerService` will automatically shutdown all the schedulers and their associated executors.

### 💻 CLI Runner

You can also run schedulers from the command line using the BoxLang CLI. This is useful for running scheduled tasks in CI/CD pipelines, containerized environments, system cron jobs, or for testing purposes. The CLI runner provides a standalone way to execute scheduler files directly from the operating system level.

#### Command Syntax

```bash
# Using BoxLang binary
boxlang schedule <SCHEDULER_FILE>

# Using Java JAR
java -jar boxlang.jar schedule <SCHEDULER_FILE>
```

#### Requirements

* **File Extension**: Must be a `.bx` (BoxLang) file
* **File Content**: Should contain a BoxLang class with scheduler definitions
* **File Path**: Can be absolute or relative to current directory

#### Lifecycle

When you run a scheduler via the CLI, BoxLang follows this lifecycle:

1. **🔍 File Validation**: The file is checked for existence and proper `.bx` extension
2. **⚙️ Compilation**: File is compiled and validated for syntax errors
3. **🏗️ Instantiation**: Scheduler class is instantiated with injected dependencies
4. **📋 Registration**: Scheduler is registered with the BoxLang SchedulerService
5. **🚀 Startup**: All configured tasks begin execution according to their schedules
6. **⏳ Blocking**: Process runs continuously until manually stopped
7. **🛑 Graceful Shutdown**: Press `Ctrl+C` to gracefully shutdown all tasks

#### Examples

```bash
# ⏰ Run a basic scheduler
boxlang schedule ./schedulers/MainScheduler.bx

# 📁 Run scheduler with absolute path
boxlang schedule /opt/myapp/schedulers/TaskRunner.bx

# 🔧 Run scheduler in project directory
cd /my/project && boxlang schedule schedulers/CronJobs.bx

# 🐛 Run with debug mode enabled
boxlang --bx-debug schedule ./MyScheduler.bx

# ⚙️ Run with custom configuration
boxlang --bx-config ./custom.json schedule ./MyScheduler.bx
```

#### CLI Help

You can get detailed help for the schedule command:

```bash
boxlang schedule --help
# or
boxlang schedule -h
```

This displays comprehensive usage information, requirements, and examples.

#### Integration with System Services

The CLI runner is perfect for integration with system-level schedulers and process managers:

**Systemd Service (Linux)**

```ini
# /etc/systemd/system/myapp-scheduler.service
[Unit]
Description=MyApp Scheduler Service
After=network.target

[Service]
Type=simple
User=myapp
WorkingDirectory=/opt/myapp
ExecStart=/usr/local/bin/boxlang schedule schedulers/MainScheduler.bx
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
```

**Docker Container**

```dockerfile
FROM ortussolutions/boxlang:latest

COPY schedulers/ /app/schedulers/
WORKDIR /app

# Run the scheduler
CMD ["boxlang", "schedule", "schedulers/MainScheduler.bx"]
```

**Cron Job (OS Level)**

```bash
# Run scheduler every hour via cron
0 * * * * /usr/local/bin/boxlang schedule /opt/myapp/schedulers/HourlyTasks.bx

# Run scheduler at system boot
@reboot /usr/local/bin/boxlang schedule /opt/myapp/schedulers/StartupTasks.bx
```

#### Environment Variables

You can use BoxLang environment variables with the CLI runner:

```bash
# Set debug mode
export BOXLANG_DEBUG=true
boxlang schedule MyScheduler.bx

# Custom configuration
export BOXLANG_CONFIG=/path/to/config.json
boxlang schedule MyScheduler.bx

# Custom runtime home
export BOXLANG_HOME=/opt/boxlang
boxlang schedule MyScheduler.bx
```

#### Logging and Monitoring

The CLI runner integrates with BoxLang's logging system:

```javascript
// In your scheduler class
class {
    property name="scheduler";
    property name="logger";

    function configure(){
        // Log scheduler startup
        logger.info( "CLI Scheduler starting up..." );

        scheduler.task( "monitoring-task" )
            .call( () => {
                logger.info( "Monitoring task executed at: " & now() );
                // Your monitoring logic here
            })
            .every( 30, "seconds" );
    }

    void function onStartup(){
        logger.info( "✅ CLI Scheduler [#scheduler.getSchedulerName()#] is now running" );
    }

    void function onShutdown(){
        logger.info( "🛑 CLI Scheduler [#scheduler.getSchedulerName()#] has been stopped" );
    }
}
```

All log files will be stored in the BoxLang Home's `logs` directory under the `scheduler.log` file. You can monitor this file for real-time updates on task execution, errors, and performance metrics.

#### Process Management

When running via CLI, you can manage the process using standard OS tools:

```bash
# Run in background
nohup boxlang schedule MyScheduler.bx > /var/log/myapp/scheduler.log 2>&1 &

# Get process ID
ps aux | grep "boxlang schedule"

# Stop gracefully (sends SIGTERM)
kill <PID>

# Force stop (sends SIGKILL)
kill -9 <PID>
```

#### Use Cases

The CLI runner is ideal for:

* **🐳 Containerized Applications**: Run schedulers in Docker/Kubernetes
* **☁️ Cloud Functions**: Execute scheduled tasks in serverless environments
* **🔄 CI/CD Pipelines**: Run maintenance tasks during deployments
* **🖥️ System Administration**: Replace traditional cron jobs with BoxLang schedulers
* **🧪 Development & Testing**: Quickly test scheduler configurations
* **📊 Data Processing**: Run ETL jobs and data synchronization tasks
* **🔍 Monitoring**: Execute health checks and system monitoring tasks

This will instantiate the scheduler, configure it, start it, and run it until it's manually stopped or all tasks complete (for one-off tasks).

## 📁 Scheduler Logging

BoxLang provides dedicated logging for all scheduling operations through the `scheduler.log` file located in the `logs` folder of your BoxLang home directory. Please leverage logging as much as possible, as in async logging is critical for debugging and monitoring executor behavior.

### Automatic Logging

All scheduler operations are automatically logged:

* Scheduler creation and configuration
* Task submissions and completions
* Shutdown events and timing
* Error conditions and exceptions
* Performance warnings

### Manual Logging

You can send custom messages to the scheduler log:

```js
// Log different message types to scheduler.log
writeLog( text: "Starting batch processing job",  log: "scheduler" ) // info message
writeLog( text: "Performance degradation detected", type: "Warning", log: "scheduler" )
writeLog( text: "Task execution failed", type: "Error", log: "scheduler" )
writeLog( text: "Debugging scheduler behavior", type: "Debug", log: "scheduler" )
writeLog( text: "Detailed execution trace", type: "Trace", log: "scheduler" )
```

**Available Log Types:**

* `"Information"` - General operational messages - (Default)
* `"Warning"` - Performance issues or concerns
* `"Error"` - Execution failures and exceptions
* `"Debug"` - Development and troubleshooting info
* `"Trace"` - Detailed execution flow information

### Log File Location

```bash
{BoxLang-Home}/logs/scheduler.log
```

Monitor this file for:

* Scheduler performance issues
* Task execution failures
* Resource exhaustion warnings
* Shutdown timing problems

## 🎛️ Scheduler Management BIFs

BoxLang provides several Built-In Functions (BIFs) for managing schedulers at runtime. These functions allow you to interact with the scheduler service programmatically and manage schedulers dynamically.

### schedulerStart()

Creates, registers, and starts a scheduler with the given instantiation class path.

**Syntax:**

```javascript
schedulerStart( className, [name], [force] )
```

**Parameters:**

* `className` (required): The class name to instantiate (e.g., "models.myapp.MyScheduler")
* `name` (optional): Override the scheduler name defined in the class
* `force` (optional): Force start the scheduler (default: true)

**Returns:** The scheduler object

**Example:**

```javascript
// Start a scheduler
myScheduler = schedulerStart( "config.MyScheduler" );

// Start with custom name
myScheduler = schedulerStart( "config.MyScheduler", "CustomName" );
```

### schedulerGet()

Get a specific scheduler by name from the scheduler service.

**Syntax:**

```javascript
schedulerGet( name )
```

**Parameters:**

* `name` (required): The name of the scheduler to retrieve

**Returns:** The scheduler object

**Throws:** `IllegalArgumentException` if scheduler not found

**Example:**

```javascript
try {
    myScheduler = schedulerGet( "MyScheduler" );
    println( "Found scheduler: " & myScheduler.getSchedulerName() );
} catch( any e ) {
    println( "Scheduler not found: " & e.message );
}
```

### schedulerGetAll()

Get all registered schedulers as a struct.

**Syntax:**

```javascript
schedulerGetAll()
```

**Returns:** A struct containing all registered schedulers (key = scheduler name, value = scheduler object)

**Example:**

```javascript
allSchedulers = schedulerGetAll();
for( schedulerName in allSchedulers ) {
    println( "Scheduler: " & schedulerName );
    println( "Tasks: " & allSchedulers[ schedulerName ].getRegisteredTasks().toString() );
}
```

### schedulerList()

List all the scheduler names registered in the system.

**Syntax:**

```javascript
schedulerList()
```

**Returns:** An array of scheduler names

**Example:**

```javascript
schedulerNames = schedulerList();
println( "Available schedulers: " & schedulerNames.toString() );
```

### schedulerShutdown()

Shutdown a scheduler by name gracefully or forcefully.

**Syntax:**

```javascript
schedulerShutdown( name, [force], [timeout] )
```

**Parameters:**

* `name` (required): The name of the scheduler to shutdown
* `force` (optional): Force shutdown the scheduler (default: false)
* `timeout` (optional): Timeout in seconds to wait for graceful shutdown (default: 30)

**Example:**

```javascript
// Graceful shutdown
schedulerShutdown( "MyScheduler" );

// Force shutdown with custom timeout
schedulerShutdown( "MyScheduler", true, 60 );
```

### schedulerRestart()

Restart a scheduler by name (shutdown then startup).

**Syntax:**

```javascript
schedulerRestart( name, [force], [timeout] )
```

**Parameters:**

* `name` (required): The name of the scheduler to restart
* `force` (optional): Force restart the scheduler (default: false)
* `timeout` (optional): Timeout in seconds to wait for shutdown (default: 30)

**Example:**

```javascript
// Graceful restart
schedulerRestart( "MyScheduler" );

// Force restart with custom timeout
schedulerRestart( "MyScheduler", true, 60 );
```

### schedulerStats()

Get statistics for all schedulers or a specific scheduler.

**Syntax:**

```javascript
schedulerStats( [name] )
```

**Parameters:**

* `name` (optional): The name of the scheduler to get stats for (if not provided, returns stats for all schedulers)

**Returns:** Stats struct(s) containing:

* `created`: When the task was created
* `lastExecutionTime`: Duration of last execution
* `lastResult`: Result of last execution
* `lastRun`: When the task last ran
* `name`: Task name
* `neverRun`: Boolean indicating if task has never run
* `nextRun`: When the task will run next
* `totalFailures`: Number of failed executions
* `totalRuns`: Total number of executions
* `totalSuccess`: Number of successful executions

**Example:**

```javascript
// Get stats for all schedulers
allStats = schedulerStats();

// Get stats for specific scheduler
myStats = schedulerStats( "MyScheduler" );
for( taskName in myStats ) {
    task = myStats[ taskName ];
    println( "Task: #taskName# - Runs: #task.totalRuns# - Failures: #task.totalFailures#" );
}
```

### 💡 Example: Dynamic Scheduler Management

Here's a practical example of how you might use these BIFs to manage schedulers dynamically:

```javascript
// Check if scheduler exists
schedulerNames = schedulerList();
if( !arrayContains( schedulerNames, "MaintenanceScheduler" ) ) {
    // Start the scheduler if it doesn't exist
    maintenanceScheduler = schedulerStart( "schedulers.MaintenanceScheduler", "MaintenanceScheduler" );
    println( "Started maintenance scheduler" );
} else {
    // Get existing scheduler
    maintenanceScheduler = schedulerGet( "MaintenanceScheduler" );
    println( "Using existing maintenance scheduler" );
}

// Get and display stats
stats = schedulerStats( "MaintenanceScheduler" );
println( "Scheduler stats: " & serializeJSON( stats ) );

// Restart scheduler if needed
if( someCondition ) {
    println( "Restarting scheduler..." );
    schedulerRestart( "MaintenanceScheduler", false, 60 );
}
```

### 📄 Task Records

When tasks are registered in a scheduler, they are wrapped in a `TaskRecord` object that contains metadata about the task's lifecycle and execution state. You can access task records through the scheduler:

```javascript
// Get a specific task record
taskRecord = scheduler.getTaskRecord( "my-task" );

// Access task record properties
println( "Task name: " & taskRecord.name );
println( "Task group: " & taskRecord.group );
println( "Registered at: " & taskRecord.registeredAt );
println( "Scheduled at: " & taskRecord.scheduledAt );
println( "Is disabled: " & taskRecord.disabled );
println( "Has error: " & taskRecord.error );
if( taskRecord.error ) {
    println( "Error message: " & taskRecord.errorMessage );
}
```

#### TaskRecord Properties

| Property       | Description                                     |
| -------------- | ----------------------------------------------- |
| `name`         | The task name                                   |
| `group`        | The task group                                  |
| `task`         | The actual ScheduledTask object                 |
| `future`       | The ScheduledFuture object for the task         |
| `scheduledAt`  | When the task was scheduled                     |
| `registeredAt` | When the task was registered                    |
| `disabled`     | Whether the task is disabled                    |
| `error`        | Whether the task had an error during scheduling |
| `errorMessage` | The error message if any                        |
| `stacktrace`   | The full stacktrace if any                      |
| `inetHost`     | The hostname where the task is running          |
| `localIp`      | The IP address of the server                    |

## 💎 Best Practices

Here are some best practices when working with BoxLang scheduled tasks:

### 🎯 Task Design

* **Keep tasks focused**: Each task should have a single responsibility
* **Handle errors gracefully**: Use `onFailure()` callbacks to handle exceptions
* **Use appropriate timing**: Consider system load when scheduling frequent tasks
* **Leverage constraints**: Use time and date constraints to avoid unnecessary executions

### 🔧 Configuration

* **Use groups**: Organize related tasks into groups for better management
* **Set meaningful names**: Use descriptive task names for easier debugging
* **Configure metadata**: Store relevant information in task metadata
* **Choose appropriate executors**: Match executor types to your workload patterns

### 📊 Monitoring

* **Track statistics**: Use `getStats()` to monitor task performance
* **Implement logging**: Use life-cycle callbacks for comprehensive logging
* **Monitor for failures**: Set up alerts for task failures
* **Review execution times**: Watch for tasks that run longer than expected

### 🚀 Performance

* **Avoid overlaps**: Use `withNoOverlaps()` for long-running tasks
* **Optimize frequencies**: Don't schedule tasks more frequently than necessary
* **Use virtual executors**: For I/O-bound tasks, consider virtual thread executors
* **Clean up resources**: Ensure tasks properly clean up any resources they use

### 🔒 Reliability

* **Handle timezone changes**: Be aware of daylight saving time impacts
* **Plan for restarts**: Design tasks to handle application restarts gracefully
* **Use constraints wisely**: Combine multiple constraints to achieve desired scheduling
* **Test thoroughly**: Test your schedulers in different scenarios and environments

```javascript
// Example of a well-designed scheduler
class {
    property name="scheduler";
    property name="logger";

    function configure(){
        // Configure scheduler
        scheduler.setSchedulerName( "ProductionScheduler" );
        scheduler.setTimezone( "UTC" );

        // High-frequency monitoring task
        scheduler.task( "health-check", "monitoring" )
            .call( () => performHealthCheck() )
            .every( 30, "seconds" )
            .setMeta({ "critical": true })
            .onFailure( function( task, exception ) {
                logger.error( "Health check failed: " & exception.getMessage() );
                alertingService.sendAlert( "CRITICAL", "Health check failure" );
            });

        // Daily maintenance task
        scheduler.task( "daily-cleanup", "maintenance" )
            .call( () => performDailyCleanup() )
            .everyDayAt( "02:00" )
            .withNoOverlaps()
            .setMeta({ "department": "ops", "notification": true })
            .before( function( task ) {
                logger.info( "Starting daily cleanup..." );
            })
            .after( function( task, result ) {
                logger.info( "Daily cleanup completed. Duration: " & task.getStats().lastExecutionTime );
            });

        // Business day report
        scheduler.task( "business-report", "reports" )
            .call( () => generateBusinessReport() )
            .weekdays( "17:00" )
            .when( function() {
                // Only run if we have data to process
                return hasDataToProcess();
            })
            .setMeta({ "type": "report", "priority": "medium" });
    }
}
```

This comprehensive guide covers all the essential aspects of BoxLang's scheduled tasks framework. Whether you're building simple cron-like jobs or complex distributed scheduling systems, BoxLang's scheduler provides the tools and flexibility you need.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://boxlang.ortusbooks.com/boxlang-framework/asynchronous-programming/scheduled-tasks.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
