[[features]] = Features [[partintro]] -- This section goes into more detail about Spring Cloud Task, including how to use it, how to configure it, and the appropriate extension points. -- [[features-lifecycle]] == The lifecycle of a Spring Cloud Task In most cases, the modern cloud environment is designed around the execution of processes that are not expected to end. If they do end, they are typically restarted. While most platforms do have some way to run a process that is not restarted when it ends, the results of that run are typically not maintained in a consumable way. Spring Cloud Task offers the ability to execute short-lived processes in an environment and record the results. Doing so allows for a microservices architecture around short-lived processes as well as longer running services through the integration of tasks by messages. While this functionality is useful in a cloud environment, the same issues can arise in a traditional deployment model as well. When running Spring Boot applications with a scheduler such as cron, it can be useful to be able to monitor the results of the application after its completion. Spring Cloud Task takes the approach that a Spring Boot application can have a start and an end and still be successful. Batch applications are one example of how processes that are expected to end (and that are often short-lived) can be helpful. Spring Cloud Task records the lifecycle events of a given task. Most long-running processes, typified by most web applications, do not save their lifecycle events. The tasks at the heart of Spring Cloud Task do. The lifecycle consists of a single task execution. This is a physical execution of a Spring Boot application configured to be a task (that is, it has the Sprint Cloud Task dependencies). At the beginning of a task, before any `CommandLineRunner` or `ApplicationRunner` implementations have been run, an entry in the `TaskRepository` that records the start event is created. This event is triggered through `SmartLifecycle#start` being triggered by the Spring Framework. This indicates to the system that all beans are ready for use and comes before running any of the `CommandLineRunner` or `ApplicationRunner` implementations provided by Spring Boot. NOTE: The recording of a task only occurs upon the successful bootstrapping of an `ApplicationContext`. If the context fails to bootstrap at all, the task's run is not recorded. Upon completion of all of the `*Runner#run` calls from Spring Boot or the failure of an `ApplicationContext` (indicated by an `ApplicationFailedEvent`), the task execution is updated in the repository with the results. NOTE: If the application requires the `ApplicationContext` to be closed at the completion of a task (all `*Runner#run` methods have been called and the task repository has been updated), set the property `spring.cloud.task.closecontextEnabled` to true. [[features-task-execution-details]] === The TaskExecution The information stored in the `TaskRepository` is modeled in the `TaskExecution` class and consists of the following information: |=== |Field |Description |`executionid` |The unique ID for the task's run. |`exitCode` |The exit code generated from an `ExitCodeExceptionMapper` implementation. If there is no exit code generated but an `ApplicationFailedEvent` is thrown, 1 is set. Otherwise, it is assumed to be 0. |`taskName` |The name for the task, as determined by the configured `TaskNameResolver`. |`startTime` |The time the task was started, as indicated by the `SmartLifecycle#start` call. |`endTime` |The time the task was completed, as indicated by the `ApplicationReadyEvent`. |`exitMessage` |Any information available at the time of exit. This can programmatically be set by a `TaskExecutionListener`. |`errorMessage` |If an exception is the cause of the end of the task (as indicated by an `ApplicationFailedEvent`), the stack trace for that exception is stored here. |`arguments` |A `List` of the string command line arguments as they were passed into the executable boot application. |=== [[features-lifecycle-exit-codes]] === Mapping Exit Codes When a task completes, it tries to return an exit code to the OS. If we take a look at our xref:getting-started.adoc#getting-started-developing-first-task[original example], we can see that we are not controlling that aspect of our application. So, if an exception is thrown, the JVM returns a code that may or may not be of any use to you in debugging. Consequently, Spring Boot provides an interface, `ExitCodeExceptionMapper`, that lets you map uncaught exceptions to exit codes. Doing so lets you indicate, at the level of exit codes, what went wrong. Also, by mapping exit codes in this manner, Spring Cloud Task records the returned exit code. If the task terminates with a SIG-INT or a SIG-TERM, the exit code is zero unless otherwise specified within the code. NOTE: While the task is running, the exit code is stored as a null in the repository. Once the task completes, the appropriate exit code is stored based on the guidelines described earlier in this section. [[features-configuration]] == Configuration Spring Cloud Task provides a ready-to-use configuration, as defined in the `DefaultTaskConfigurer` and `SimpleTaskConfiguration` classes. This section walks through the defaults and how to customize Spring Cloud Task for your needs. [[features-data-source]] === DataSource Spring Cloud Task uses a datasource for storing the results of task executions. By default, we provide an in-memory instance of H2 to provide a simple method of bootstrapping development. However, in a production environment, you probably want to configure your own `DataSource`. If your application uses only a single `DataSource` and that serves as both your business schema and the task repository, all you need to do is provide any `DataSource` (the easiest way to do so is through Spring Boot's configuration conventions). This `DataSource` is automatically used by Spring Cloud Task for the repository. If your application uses more than one `DataSource`, you need to configure the task repository with the appropriate `DataSource`. This customization can be done through an implementation of `TaskConfigurer`. [[features-table-prefix]] === Table Prefix One modifiable property of `TaskRepository` is the table prefix for the task tables. By default, they are all prefaced with `TASK_`. `TASK_EXECUTION` and `TASK_EXECUTION_PARAMS` are two examples. However, there are potential reasons to modify this prefix. If the schema name needs to be prepended to the table names or if more than one set of task tables is needed within the same schema, you must change the table prefix. You can do so by setting the `spring.cloud.task.tablePrefix` to the prefix you need, as follows: `spring.cloud.task.tablePrefix=yourPrefix` By using the `spring.cloud.task.tablePrefix`, a user assumes the responsibility to create the task tables that meet both the criteria for the task table schema but with modifications that are required for a user's business needs. You can utilize the Spring Cloud Task Schema DDL as a guide when creating your own Task DDL as seen https://github.com/spring-cloud/spring-cloud-task/tree/master/spring-cloud-task-core/src/main/resources/org/springframework/cloud/task[here]. [[features-table-initialization]] === Enable/Disable table initialization In cases where you are creating the task tables and do not wish for Spring Cloud Task to create them at task startup, set the `spring.cloud.task.initialize-enabled` property to `false`, as follows: `spring.cloud.task.initialize-enabled=false` It defaults to `true`. NOTE: The property `spring.cloud.task.initialize.enable` has been deprecated. [[features-generated_task_id]] === Externally Generated Task ID In some cases, you may want to allow for the time difference between when a task is requested and when the infrastructure actually launches it. Spring Cloud Task lets you create a `TaskExecution` when the task is requested. Then pass the execution ID of the generated `TaskExecution` to the task so that it can update the `TaskExecution` through the task's lifecycle. A `TaskExecution` can be created by calling the `createTaskExecution` method on an implementation of the `TaskRepository` that references the datastore that holds the `TaskExecution` objects. In order to configure your Task to use a generated `TaskExecutionId`, add the following property: `spring.cloud.task.executionid=yourtaskId` [[features-external_task_id]] === External Task Id Spring Cloud Task lets you store an external task ID for each `TaskExecution`. In order to configure your Task to use a generated `TaskExecutionId`, add the following property: `spring.cloud.task.external-execution-id=` [[features-parent_task_id]] === Parent Task Id Spring Cloud Task lets you store a parent task ID for each `TaskExecution`. An example of this would be a task that executes another task or tasks and you want to record which task launched each of the child tasks. In order to configure your Task to set a parent `TaskExecutionId` add the following property on the child task: `spring.cloud.task.parent-execution-id=` [[features-task-configurer]] === TaskConfigurer The `TaskConfigurer` is a strategy interface that lets you customize the way components of Spring Cloud Task are configured. By default, we provide the `DefaultTaskConfigurer` that provides logical defaults: `Map`-based in-memory components (useful for development if no `DataSource` is provided) and JDBC based components (useful if there is a `DataSource` available). The `TaskConfigurer` lets you configure three main components: |=== |Component |Description |Default (provided by `DefaultTaskConfigurer`) |`TaskRepository` |The implementation of the `TaskRepository` to be used. |`SimpleTaskRepository` |`TaskExplorer` |The implementation of the `TaskExplorer` (a component for read-only access to the task repository) to be used. |`SimpleTaskExplorer` |`PlatformTransactionManager` |A transaction manager to be used when running updates for tasks. |`JdbcTransactionManager` if a `DataSource` is used. `ResourcelessTransactionManager` if it is not. |=== You can customize any of the components described in the preceding table by creating a custom implementation of the `TaskConfigurer` interface. Typically, extending the `DefaultTaskConfigurer` (which is provided if a `TaskConfigurer` is not found) and overriding the required getter is sufficient. However, implementing your own from scratch may be required. NOTE: Users should not directly use getter methods from a `TaskConfigurer` directly unless they are using it to supply implementations to be exposed as Spring Beans. [[features-task-execution-listener]] === Task Execution Listener `TaskExecutionListener` lets you register listeners for specific events that occur during the task lifecycle. To do so, create a class that implements the `TaskExecutionListener` interface. The class that implements the `TaskExecutionListener` interface is notified of the following events: * `onTaskStartup`: Prior to storing the `TaskExecution` into the `TaskRepository`. * `onTaskEnd`: Prior to updating the `TaskExecution` entry in the `TaskRepository` and marking the final state of the task. * `onTaskFailed`: Prior to the `onTaskEnd` method being invoked when an unhandled exception is thrown by the task. Spring Cloud Task also lets you add `TaskExecution` Listeners to methods within a bean by using the following method annotations: * `@BeforeTask`: Prior to the storing the `TaskExecution` into the `TaskRepository` * `@AfterTask`: Prior to the updating of the `TaskExecution` entry in the `TaskRepository` marking the final state of the task. * `@FailedTask`: Prior to the `@AfterTask` method being invoked when an unhandled exception is thrown by the task. The following example shows the three annotations in use: [source,java] ---- public class MyBean { @BeforeTask public void methodA(TaskExecution taskExecution) { } @AfterTask public void methodB(TaskExecution taskExecution) { } @FailedTask public void methodC(TaskExecution taskExecution, Throwable throwable) { } } ---- NOTE: Inserting an `ApplicationListener` earlier in the chain than `TaskLifecycleListener` exists may cause unexpected effects. [[features-task-execution-listener-Exceptions]] ==== Exceptions Thrown by Task Execution Listener If an exception is thrown by a `TaskExecutionListener` event handler, all listener processing for that event handler stops. For example, if three `onTaskStartup` listeners have started and the first `onTaskStartup` event handler throws an exception, the other two `onTaskStartup` methods are not called. However, the other event handlers (`onTaskEnd` and `onTaskFailed`) for the `TaskExecutionListeners` are called. The exit code returned when a exception is thrown by a `TaskExecutionListener` event handler is the exit code that was reported by the https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/ExitCodeEvent.html[ExitCodeEvent]. If no `ExitCodeEvent` is emitted, the Exception thrown is evaluated to see if it is of type https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-application-exit[ExitCodeGenerator]. If so, it returns the exit code from the `ExitCodeGenerator`. Otherwise, `1` is returned. In the case that an exception is thrown in an `onTaskStartup` method, the exit code for the application will be `1`. If an exception is thrown in either a `onTaskEnd` or `onTaskFailed` method, the exit code for the application will be the one established using the rules enumerated above. NOTE: In the case of an exception being thrown in a `onTaskStartup`, `onTaskEnd`, or `onTaskFailed` you can not override the exit code for the application using `ExitCodeExceptionMapper`. [[features-task-execution-listener-exit-messages]] ==== Exit Messages You can set the exit message for a task programmatically by using a `TaskExecutionListener`. This is done by setting the `TaskExecution's` `exitMessage`, which then gets passed into the `TaskExecutionListener`. The following example shows a method that is annotated with the `@AfterTask` `ExecutionListener` : [source,java] @AfterTask public void afterMe(TaskExecution taskExecution) { taskExecution.setExitMessage("AFTER EXIT MESSAGE"); } An `ExitMessage` can be set at any of the listener events (`onTaskStartup`, `onTaskFailed`, and `onTaskEnd`). The order of precedence for the three listeners follows: . `onTaskEnd` . `onTaskFailed` . `onTaskStartup` For example, if you set an `exitMessage` for the `onTaskStartup` and `onTaskFailed` listeners and the task ends without failing, the `exitMessage` from the `onTaskStartup` is stored in the repository. Otherwise, if a failure occurs, the `exitMessage` from the `onTaskFailed` is stored. Also if you set the `exitMessage` with an `onTaskEnd` listener, the `exitMessage` from the `onTaskEnd` supersedes the exit messages from both the `onTaskStartup` and `onTaskFailed`. [[features-single-instance-enabled]] === Restricting Spring Cloud Task Instances Spring Cloud Task lets you establish that only one task with a given task name can be run at a time. To do so, you need to establish the <> and set `spring.cloud.task.single-instance-enabled=true` for each task execution. While the first task execution is running, any other time you try to run a task with the same <> and `spring.cloud.task.single-instance-enabled=true`, the task fails with the following error message: `Task with name "application" is already running.` The default value for `spring.cloud.task.single-instance-enabled` is `false`. The following example shows how to set `spring.cloud.task.single-instance-enabled` to `true`: `spring.cloud.task.single-instance-enabled=true or false` To use this feature, you must add the following Spring Integration dependencies to your application: [source,xml] org.springframework.integration spring-integration-core org.springframework.integration spring-integration-jdbc NOTE: The exit code for the application will be 1 if the task fails because this feature is enabled and another task is running with the same task name. [[single-instance-usage-for-spring-aot-and-native-compilation]] ==== Single Instance Usage for Spring AOT And Native Compilation To use Spring Cloud Task's single-instance feature when creating a natively compiled app, you need to enable the feature at build time. To do so, add the process-aot execution and set `spring.cloud.task.single-step-instance-enabled=true` as a JVM argument, as follows: [source,xml] org.springframework.boot spring-boot-maven-plugin process-aot process-aot -Dspring.cloud.task.single-instance-enabled=true [[enabling-observations-for-applicationrunner-and-commandlinerunner]] === Enabling Observations for ApplicationRunner and CommandLineRunner To Enable Task Observations for `ApplicationRunner` or `CommandLineRunner` set `spring.cloud.task.observation.enabled` to true. An example task application with observations enables using the `SimpleMeterRegistry` can be found https://github.com/spring-cloud/spring-cloud-task/tree/main/spring-cloud-task-samples/task-observations[here]. [[disabling-spring-cloud-task-auto-configuration]] === Disabling Spring Cloud Task Auto Configuration In cases where Spring Cloud Task should not be autoconfigured for an implementation, you can disable Task's auto configuration. This can be done either by adding the following annotation to your Task application: ``` @EnableAutoConfiguration(exclude={SimpleTaskAutoConfiguration.class}) ``` You may also disable Task auto configuration by setting the `spring.cloud.task.autoconfiguration.enabled` property to `false`. [[closing-the-context]] === Closing the Context If the application requires the `ApplicationContext` to be closed at the completion of a task (all `*Runner#run` methods have been called and the task repository has been updated), set the property `spring.cloud.task.closecontextEnabled` to `true`. Another case to close the context is when the Task Execution completes however the application does not terminate. In these cases the context is held open because a thread has been allocated (for example: if you are using a TaskExecutor). In these cases set the `spring.cloud.task.closecontextEnabled` property to `true` when launching your task. This will close the application's context once the task is complete. Thus allowing the application to terminate. [[enable-task-metrics]] === Enable Task Metrics Spring Cloud Task integrates with Micrometer and creates observations for the Tasks it executes. To enable Task Observability integration, you must add `spring-boot-starter-actuator`, your preferred registry implementation (if you want to publish metrics), and micrometer-tracing (if you want to publish tracing data) to your task application. An example maven set of dependencies to enable task observability and metrics using Influx would be: [source,xml] org.springframework.boot spring-boot-starter-actuator io.micrometer micrometer-registry-influx runtime [[spring-task-and-spring-cloud-task]] === Spring Task and Spring Cloud Task Properties The term `task` is frequently used word in the industry. In one such example Spring Boot offers the `spring.task` while Spring Cloud Task offers the `spring.cloud.task` properties. This has caused some confusion in the past that these two groups of properties are directly related. However, they represent 2 different set of features offered in the Spring ecosystem. * `spring.task` refers to the properties that configure the `ThreadPoolTaskScheduler`. * `spring.cloud.task` refers to the properties that configure features of Spring Cloud Task.