diff --git a/articles/flow/advanced/service-executor.adoc b/articles/flow/advanced/service-executor.adoc new file mode 100644 index 0000000000..7ebc19838f --- /dev/null +++ b/articles/flow/advanced/service-executor.adoc @@ -0,0 +1,131 @@ +--- +title: Vaadin Executor +page-title: How to configure and use Vaadin built-in Executor +description: Configuring and using Vaadin Executor to run background tasks. +meta-description: Discover how to configure and use Vaadin Executor to run background tasks. +order: 177 +--- + + += Vaadin Executor + +The Vaadin Executor provides a centralized mechanism for managing asynchronous tasks in Vaadin applications. +It offers a configurable thread pool that can be used for executing background operations without blocking the UI thread. + +This feature is particularly useful for performing time-consuming operations asynchronously, executing background tasks +that should not block the UI, managing concurrent operations efficiently and integrating with framework-specific task execution mechanisms, +like Spring [classname]`TaskExecutor` or CDI [classname]`ManagedExecutor`. + +By default, Vaadin creates a thread pool executor with the following configuration: + +* Core pool size: 8 threads +* Maximum pool size: Unbounded (Integer.MAX_VALUE) +* Keep-alive time: 60 seconds for idle threads +* Custom thread factory that creates daemon threads +* Core threads are allowed to time out when idle + +This default configuration is suitable for most applications but can be customized as needed. + +== Accessing and Using the Executor + +You can access the executor service through the [classname]`VaadinService` instance: + +[source,java] +---- +VaadinService service = VaadinService.getCurrent(); +Executor executor = service.getExecutor(); + +// Execute a task asynchronously +executor.execute(() -> { + // Your background task here + service.longRunningTask(); +}); + +// Execute a task asynchronously using CompletableFeature +CompletableFuture.supplyAsync(service::longRunningTask, executor); + .thenAccept(this::computeResult); +---- + +You can use the executor service in your UI components to perform background operations and update the UI when complete. + +[source,java] +---- +Button button = new Button("Start Background Task"); +button.addClickListener(event -> { + UI ui = UI.getCurrent(); + VaadinService.getCurrent().getExecutor().execute(() -> { + + // Perform time-consuming operation + service.longRunningTask(); + + // Update UI from background thread + ui.access(() -> { + Notification.show("Background task completed!"); + }); + }); +}); +---- + +Remember that code running in the executor is not automatically thread-safe: + +* Use `UI.access()` to update the UI from background threads. +* Be careful with shared state and consider using thread-safe collections or synchronization. +* Avoid long-running tasks that might block the thread pool. + +== Configuring a Custom Executor + +In standard Java applications, you can customize the executor by registering a <> and providing your own executor implementation: + +.`CustomExecutorServiceInitListener.java` +[source,java] +---- +package com.example; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import com.vaadin.flow.server.ServiceInitEvent; +import com.vaadin.flow.server.VaadinServiceInitListener; + +public class CustomExecutorServiceInitListener implements VaadinServiceInitListener { + @Override + public void serviceInit(ServiceInitEvent event) { + ThreadPoolExecutor customExecutor = new ThreadPoolExecutor( + 16, // Core pool size + 32, // Maximum pool size + 120, // Keep-alive time + TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), + r -> { + Thread thread = new Thread(r, "CustomVaadinExecutor-" + r.hashCode()); + thread.setDaemon(true); + return thread; + }); + + // Allow core threads to time out + customExecutor.allowCoreThreadTimeOut(true); + + // Set the custom executor + event.setExecutor(customExecutor); + } +} +---- + +Register your listener using Java's `ServiceLoader` mechanism by creating a file at +`META-INF/services/com.vaadin.flow.server.VaadinServiceInitListener`. +The file should contain the fully qualified name of your implementation class: +`com.example.CustomExecutorServiceInitListener` + +When configuring a custom Thread Pool, consider the following best practices: + +* *Core Pool Size*: Set based on the number of concurrent tasks your application typically handles. A good starting point is the number of CPU cores. +* *Maximum Pool Size*: Set to a reasonable upper limit to prevent resource exhaustion. Consider your server's memory and CPU constraints. +* *Queue Capacity*: Use a bounded queue to prevent memory issues when task submission rate exceeds execution rate. +* Use descriptive thread names to make debugging easier. +* Make threads daemon threads (`Thread.setDaemon(true)`) to prevent them from blocking JVM shutdown. +* Allow core threads to time out if your application has periods of inactivity + +For framework integrations, consult the specific documentation pages for <<../integrations/spring/configuration#configure-custom-vaadin-executor,Spring>>, <<../integrations/cdi/service-beans#configure-custom-vaadin-executor,CDI>> and <<../integrations/quarkus#configure-custom-vaadin-executor,Quarkus>>. + + +[discussion-id]`2EF7B593-E426-479E-89D7-E62667D316C2` \ No newline at end of file diff --git a/articles/flow/integrations/cdi/service-beans.adoc b/articles/flow/integrations/cdi/service-beans.adoc index 963d8c1566..49abac17d3 100644 --- a/articles/flow/integrations/cdi/service-beans.adoc +++ b/articles/flow/integrations/cdi/service-beans.adoc @@ -17,6 +17,7 @@ The https://vaadin.com/directory/component/vaadin-cdi/[Vaadin CDI] add-on refere - [interfacename]`InstantiatorFactory` - [interfacename]`SystemMessagesProvider` - [interfacename]`ErrorHandler` +- [interfacename]`Executor` To ensure the beans are recognized, they should be qualified by the `@VaadinServiceEnabled` annotation. This is required because it marks a bean which is used as an `I18NProvider` by the service. If there are any other `I18NProvider` beans, the one that's also used by the service is used. @@ -55,4 +56,48 @@ The purpose of `@VaadinServiceScoped` is to define a context of the lifespan of For example, there's no guarantee that an active Vaadin session or UI context exists when the add-on looks for any of these beans. It's safe, though, to use standard CDI `@Dependent` and `@ApplicationScoped` contexts. +== Configure Custom Vaadin Executor + +When a Vaadin application starts in a CDI environment, the [classname]`CdiVaadinServletService` overrides the `createDefaultExecutor()` method to use CDI capabilities for selecting an appropriate [interfacename]`Executor` for executing Vaadin's internal tasks. The selection process follows this order: + +1. Look for a custom [interfacename]`Executor` bean qualified with the [annotationname]`@VaadinServiceEnabled` annotation +2. If no custom executor is found, try to use the container's [interfacename]`ManagedExecutorService` (available in Jakarta EE environments) +3. If neither is available, fall back to Vaadin's <<{articles}/flow/advanced/service-executor#, default executor>> implementation + +To avoid ambiguity, the CDI extension enforces these rules: + +* There can be at most one [interfacename]`Executor` bean annotated with [annotationname]`@VaadinServiceEnabled` +* If multiple [interfacename]`Executor` beans with [annotationname]`@VaadinServiceEnabled` annotation are found, the application deployment will fail with an appropriate error message + + +You can provide your own task executor to customize how Vaadin executes server-side operations. This can be useful for implementing custom threading models, using specific thread pools, or integrating with other execution frameworks. +To provide a custom executor, define a CDI bean that implements the [interfacename]`Executor` interface and annotate it with [annotationname]`@VaadinServiceEnabled`: + +[source,java] +---- +@ApplicationScoped +public class ExecutorConfiguration { + + @Produces + @Singleton + @VaadinServiceEnabled + public Executor vaadinTaskExecutor() { + ThreadPoolExecutor customExecutor = new ThreadPoolExecutor( + 16, // Core pool size + 32, // Maximum pool size + 120, // Keep-alive time + TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), + r -> { + Thread thread = new Thread(r, "CustomVaadinExecutor-" + r.hashCode()); + thread.setDaemon(true); + return thread; + }); + customExecutor.allowCoreThreadTimeOut(true); + return customExecutor; + } +} +---- + + [discussion-id]`A55D3416-D5B7-40B9-8C4A-1454E97C92F1` diff --git a/articles/flow/integrations/quarkus.adoc b/articles/flow/integrations/quarkus.adoc index 61b6db7398..67d869d420 100644 --- a/articles/flow/integrations/quarkus.adoc +++ b/articles/flow/integrations/quarkus.adoc @@ -129,6 +129,50 @@ See these documentation pages for Vaadin CDI features: Quarkus attempts to remove by default all unused beans during the build. However, it can't detect the programmatic lookup performed by the [classname]`QuarkusInstantiator` class in Vaadin. Therefore, when implementing beans that are not referenced directly by the application code, you might need to add the [annotationname]`@Unremovable` annotation to the class. + [since:com.vaadin:vaadin@V24.8]##[annotationname]`@VaadinServiceEnabled` annotated beans are marked as unremovable by default.## +== Configure Custom Vaadin Executor + +When a Vaadin application starts in a Quarkus environment, the [classname]`QuarkusVaadinServletService` overrides the `createDefaultExecutor()` method to use CDI capabilities for selecting an appropriate [interfacename]`Executor` for executing Vaadin's internal tasks. The selection process follows this order: + +1. Look for a custom [interfacename]`Executor` bean qualified with the [annotationname]`@VaadinServiceEnabled` annotation +2. If no custom executor is found, try to use the container's [interfacename]`ManagedExecutor` +3. If neither is available, fall back to Vaadin's <<{articles}/flow/advanced/service-executor#, default executor>> implementation + +To avoid ambiguity, the Quarkus extension enforces these rules: + +* There can be at most one [interfacename]`Executor` bean annotated with [annotationname]`@VaadinServiceEnabled` +* If multiple [interfacename]`Executor` beans with [annotationname]`@VaadinServiceEnabled` annotation are found, the application deployment will fail with an appropriate error message + + +You can provide your own task executor to customize how Vaadin executes server-side operations. This can be useful for implementing custom threading models, using specific thread pools, or integrating with other execution frameworks. +To provide a custom executor, define a CDI bean that implements the [interfacename]`Executor` interface and annotate it with [annotationname]`@VaadinServiceEnabled`: + +[source,java] +---- +@ApplicationScoped +public class ExecutorConfiguration { + + @Produces + @Singleton + @VaadinServiceEnabled + public Executor vaadinTaskExecutor() { + ThreadPoolExecutor customExecutor = new ThreadPoolExecutor( + 16, // Core pool size + 32, // Maximum pool size + 120, // Keep-alive time + TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), + r -> { + Thread thread = new Thread(r, "CustomVaadinExecutor-" + r.hashCode()); + thread.setDaemon(true); + return thread; + }); + customExecutor.allowCoreThreadTimeOut(true); + return customExecutor; + } +} +---- + + [[quarkus.vaadin.addons]] == Vaadin Add-Ons in Quarkus diff --git a/articles/flow/integrations/spring/configuration.adoc b/articles/flow/integrations/spring/configuration.adoc index 7892459aec..d1e15f2136 100644 --- a/articles/flow/integrations/spring/configuration.adoc +++ b/articles/flow/integrations/spring/configuration.adoc @@ -3,6 +3,7 @@ title: Configuration page-title: Spring Configuration for Vaadin Applications description: Configuring Spring properties in a Vaadin application. meta-description: Configure Spring applications with Vaadin for seamless integration. +layout: tabbed-page order: 60 --- @@ -215,6 +216,73 @@ FilterRegistrationBean publicImagesAliasFilter(@Value("${vaadin.url-mapping}" } ---- +== Configure Custom Vaadin Executor + +When running a Vaadin application in a Spring environment, the [classname]`SpringVaadinServletService` automatically selects an appropriate executor for asynchronous operations using the following process: + +. Look for [interfacename]`TaskExecutor` beans defined in the Spring application context +. If no [interfacename]`TaskExecutor` beans are found, fall back to Vaadin's <<{articles}/flow/advanced/service-executor#, default executor>> +. If [interfacename]`TaskExecutor` beans are found, apply specific selection rules to choose the most appropriate one + +When multiple [interfacename]`TaskExecutor` beans exist in your application, Vaadin follows these rules to select the appropriate one: + +. If any bean is annotated with [annotationname]`@VaadinTaskExecutor` or named `VaadinTaskExecutor`, it will be chosen +. If multiple beans remain after this filtering, regular executors are preferred over schedulers +. If Spring's default `applicationTaskExecutor` is among multiple candidates, it is discarded to prefer application-defined beans +. If multiple candidates still remain, Vaadin will throw an [classname]`IllegalStateException` with suggestions to resolve the conflict + +There are several ways to customize the executor used by Vaadin in your Spring application. +If you define a single [interfacename]`TaskExecutor` bean in your Spring application context, Vaadin will automatically use it. +If you have multiple [interfacename]`TaskExecutor` beans in your application, you can specifically designate one for Vaadin using the `@VaadinTaskExecutor` annotation or you can name your bean `VaadinTaskExecutor`. + +[.example] +-- + +[source,java] +---- + +@Bean +public TaskExecutor vaadinExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(10); + executor.setMaxPoolSize(50); + executor.setQueueCapacity(100); + executor.setThreadNamePrefix("vaadin-custom-executor-"); + return executor; +} +---- + +[source,java] +---- + +@Bean +@VaadinTaskExecutor +public TaskExecutor vaadinSpecificExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(10); + executor.setMaxPoolSize(50); + executor.setQueueCapacity(100); + executor.setThreadNamePrefix("vaadin-annotated-executor-"); + return executor; +} +---- + +[source,java] +---- + +@Bean("VaadinTaskExecutor") +public TaskExecutor customVaadinExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(10); + executor.setMaxPoolSize(50); + executor.setQueueCapacity(100); + executor.setThreadNamePrefix("vaadin-named-executor-"); + return executor; +} +---- + +-- + == Configure Spring MVC Applications