Skip to content

Commit 7267213

Browse files
mcollovatitltvmshabarov
authored
feat: add documentation for Vaadin Executor (#4450)
Fixes #4344 Co-authored-by: Tomi Virtanen <[email protected]> --------- Co-authored-by: Tomi Virtanen <[email protected]> Co-authored-by: Mikhail Shabarov <[email protected]>
1 parent 232489b commit 7267213

File tree

4 files changed

+288
-0
lines changed

4 files changed

+288
-0
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
---
2+
title: Vaadin Executor
3+
page-title: How to configure and use Vaadin built-in Executor
4+
description: Configuring and using Vaadin Executor to run background tasks.
5+
meta-description: Discover how to configure and use Vaadin Executor to run background tasks.
6+
order: 177
7+
---
8+
9+
10+
= Vaadin Executor
11+
12+
The Vaadin Executor provides a centralized mechanism for managing asynchronous tasks in Vaadin applications.
13+
It offers a configurable thread pool that can be used for executing background operations without blocking the UI thread.
14+
15+
This feature is particularly useful for performing time-consuming operations asynchronously, executing background tasks
16+
that should not block the UI, managing concurrent operations efficiently and integrating with framework-specific task execution mechanisms,
17+
like Spring [classname]`TaskExecutor` or CDI [classname]`ManagedExecutor`.
18+
19+
By default, Vaadin creates a thread pool executor with the following configuration:
20+
21+
* Core pool size: 8 threads
22+
* Maximum pool size: Unbounded (Integer.MAX_VALUE)
23+
* Keep-alive time: 60 seconds for idle threads
24+
* Custom thread factory that creates daemon threads
25+
* Core threads are allowed to time out when idle
26+
27+
This default configuration is suitable for most applications but can be customized as needed.
28+
29+
== Accessing and Using the Executor
30+
31+
You can access the executor service through the [classname]`VaadinService` instance:
32+
33+
[source,java]
34+
----
35+
VaadinService service = VaadinService.getCurrent();
36+
Executor executor = service.getExecutor();
37+
38+
// Execute a task asynchronously
39+
executor.execute(() -> {
40+
// Your background task here
41+
service.longRunningTask();
42+
});
43+
44+
// Execute a task asynchronously using CompletableFeature
45+
CompletableFuture.supplyAsync(service::longRunningTask, executor);
46+
.thenAccept(this::computeResult);
47+
----
48+
49+
You can use the executor service in your UI components to perform background operations and update the UI when complete.
50+
51+
[source,java]
52+
----
53+
Button button = new Button("Start Background Task");
54+
button.addClickListener(event -> {
55+
UI ui = UI.getCurrent();
56+
VaadinService.getCurrent().getExecutor().execute(() -> {
57+
58+
// Perform time-consuming operation
59+
service.longRunningTask();
60+
61+
// Update UI from background thread
62+
ui.access(() -> {
63+
Notification.show("Background task completed!");
64+
});
65+
});
66+
});
67+
----
68+
69+
Remember that code running in the executor is not automatically thread-safe:
70+
71+
* Use `UI.access()` to update the UI from background threads.
72+
* Be careful with shared state and consider using thread-safe collections or synchronization.
73+
* Avoid long-running tasks that might block the thread pool.
74+
75+
== Configuring a Custom Executor
76+
77+
In standard Java applications, you can customize the executor by registering a <<service-init-listener#,VaadinServiceInitListener>> and providing your own executor implementation:
78+
79+
.`CustomExecutorServiceInitListener.java`
80+
[source,java]
81+
----
82+
package com.example;
83+
import java.util.concurrent.LinkedBlockingQueue;
84+
import java.util.concurrent.ThreadPoolExecutor;
85+
import java.util.concurrent.TimeUnit;
86+
87+
import com.vaadin.flow.server.ServiceInitEvent;
88+
import com.vaadin.flow.server.VaadinServiceInitListener;
89+
90+
public class CustomExecutorServiceInitListener implements VaadinServiceInitListener {
91+
@Override
92+
public void serviceInit(ServiceInitEvent event) {
93+
ThreadPoolExecutor customExecutor = new ThreadPoolExecutor(
94+
16, // Core pool size
95+
32, // Maximum pool size
96+
120, // Keep-alive time
97+
TimeUnit.SECONDS,
98+
new LinkedBlockingQueue<>(),
99+
r -> {
100+
Thread thread = new Thread(r, "CustomVaadinExecutor-" + r.hashCode());
101+
thread.setDaemon(true);
102+
return thread;
103+
});
104+
105+
// Allow core threads to time out
106+
customExecutor.allowCoreThreadTimeOut(true);
107+
108+
// Set the custom executor
109+
event.setExecutor(customExecutor);
110+
}
111+
}
112+
----
113+
114+
Register your listener using Java's `ServiceLoader` mechanism by creating a file at
115+
`META-INF/services/com.vaadin.flow.server.VaadinServiceInitListener`.
116+
The file should contain the fully qualified name of your implementation class:
117+
`com.example.CustomExecutorServiceInitListener`
118+
119+
When configuring a custom Thread Pool, consider the following best practices:
120+
121+
* *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.
122+
* *Maximum Pool Size*: Set to a reasonable upper limit to prevent resource exhaustion. Consider your server's memory and CPU constraints.
123+
* *Queue Capacity*: Use a bounded queue to prevent memory issues when task submission rate exceeds execution rate.
124+
* Use descriptive thread names to make debugging easier.
125+
* Make threads daemon threads (`Thread.setDaemon(true)`) to prevent them from blocking JVM shutdown.
126+
* Allow core threads to time out if your application has periods of inactivity
127+
128+
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>>.
129+
130+
131+
[discussion-id]`2EF7B593-E426-479E-89D7-E62667D316C2`

articles/flow/integrations/cdi/service-beans.adoc

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ The https://vaadin.com/directory/component/vaadin-cdi/[Vaadin CDI] add-on refere
1717
- [interfacename]`InstantiatorFactory`
1818
- [interfacename]`SystemMessagesProvider`
1919
- [interfacename]`ErrorHandler`
20+
- [interfacename]`Executor`
2021

2122
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.
2223

@@ -55,4 +56,48 @@ The purpose of `@VaadinServiceScoped` is to define a context of the lifespan of
5556
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.
5657

5758

59+
== Configure Custom Vaadin Executor
60+
61+
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:
62+
63+
1. Look for a custom [interfacename]`Executor` bean qualified with the [annotationname]`@VaadinServiceEnabled` annotation
64+
2. If no custom executor is found, try to use the container's [interfacename]`ManagedExecutorService` (available in Jakarta EE environments)
65+
3. If neither is available, fall back to Vaadin's <<{articles}/flow/advanced/service-executor#, default executor>> implementation
66+
67+
To avoid ambiguity, the CDI extension enforces these rules:
68+
69+
* There can be at most one [interfacename]`Executor` bean annotated with [annotationname]`@VaadinServiceEnabled`
70+
* If multiple [interfacename]`Executor` beans with [annotationname]`@VaadinServiceEnabled` annotation are found, the application deployment will fail with an appropriate error message
71+
72+
73+
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.
74+
To provide a custom executor, define a CDI bean that implements the [interfacename]`Executor` interface and annotate it with [annotationname]`@VaadinServiceEnabled`:
75+
76+
[source,java]
77+
----
78+
@ApplicationScoped
79+
public class ExecutorConfiguration {
80+
81+
@Produces
82+
@Singleton
83+
@VaadinServiceEnabled
84+
public Executor vaadinTaskExecutor() {
85+
ThreadPoolExecutor customExecutor = new ThreadPoolExecutor(
86+
16, // Core pool size
87+
32, // Maximum pool size
88+
120, // Keep-alive time
89+
TimeUnit.SECONDS,
90+
new LinkedBlockingQueue<>(),
91+
r -> {
92+
Thread thread = new Thread(r, "CustomVaadinExecutor-" + r.hashCode());
93+
thread.setDaemon(true);
94+
return thread;
95+
});
96+
customExecutor.allowCoreThreadTimeOut(true);
97+
return customExecutor;
98+
}
99+
}
100+
----
101+
102+
58103
[discussion-id]`A55D3416-D5B7-40B9-8C4A-1454E97C92F1`

articles/flow/integrations/quarkus.adoc

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,50 @@ See these documentation pages for Vaadin CDI features:
129129
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. +
130130
[since:com.vaadin:[email protected]]##[annotationname]`@VaadinServiceEnabled` annotated beans are marked as unremovable by default.##
131131

132+
== Configure Custom Vaadin Executor
133+
134+
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:
135+
136+
1. Look for a custom [interfacename]`Executor` bean qualified with the [annotationname]`@VaadinServiceEnabled` annotation
137+
2. If no custom executor is found, try to use the container's [interfacename]`ManagedExecutor`
138+
3. If neither is available, fall back to Vaadin's <<{articles}/flow/advanced/service-executor#, default executor>> implementation
139+
140+
To avoid ambiguity, the Quarkus extension enforces these rules:
141+
142+
* There can be at most one [interfacename]`Executor` bean annotated with [annotationname]`@VaadinServiceEnabled`
143+
* If multiple [interfacename]`Executor` beans with [annotationname]`@VaadinServiceEnabled` annotation are found, the application deployment will fail with an appropriate error message
144+
145+
146+
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.
147+
To provide a custom executor, define a CDI bean that implements the [interfacename]`Executor` interface and annotate it with [annotationname]`@VaadinServiceEnabled`:
148+
149+
[source,java]
150+
----
151+
@ApplicationScoped
152+
public class ExecutorConfiguration {
153+
154+
@Produces
155+
@Singleton
156+
@VaadinServiceEnabled
157+
public Executor vaadinTaskExecutor() {
158+
ThreadPoolExecutor customExecutor = new ThreadPoolExecutor(
159+
16, // Core pool size
160+
32, // Maximum pool size
161+
120, // Keep-alive time
162+
TimeUnit.SECONDS,
163+
new LinkedBlockingQueue<>(),
164+
r -> {
165+
Thread thread = new Thread(r, "CustomVaadinExecutor-" + r.hashCode());
166+
thread.setDaemon(true);
167+
return thread;
168+
});
169+
customExecutor.allowCoreThreadTimeOut(true);
170+
return customExecutor;
171+
}
172+
}
173+
----
174+
175+
132176

133177
[[quarkus.vaadin.addons]]
134178
== Vaadin Add-Ons in Quarkus

articles/flow/integrations/spring/configuration.adoc

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ title: Configuration
33
page-title: Spring Configuration for Vaadin Applications
44
description: Configuring Spring properties in a Vaadin application.
55
meta-description: Configure Spring applications with Vaadin for seamless integration.
6+
layout: tabbed-page
67
order: 60
78
---
89

@@ -215,6 +216,73 @@ FilterRegistrationBean<?> publicImagesAliasFilter(@Value("${vaadin.url-mapping}"
215216
}
216217
----
217218

219+
== Configure Custom Vaadin Executor
220+
221+
When running a Vaadin application in a Spring environment, the [classname]`SpringVaadinServletService` automatically selects an appropriate executor for asynchronous operations using the following process:
222+
223+
. Look for [interfacename]`TaskExecutor` beans defined in the Spring application context
224+
. If no [interfacename]`TaskExecutor` beans are found, fall back to Vaadin's <<{articles}/flow/advanced/service-executor#, default executor>>
225+
. If [interfacename]`TaskExecutor` beans are found, apply specific selection rules to choose the most appropriate one
226+
227+
When multiple [interfacename]`TaskExecutor` beans exist in your application, Vaadin follows these rules to select the appropriate one:
228+
229+
. If any bean is annotated with [annotationname]`@VaadinTaskExecutor` or named `VaadinTaskExecutor`, it will be chosen
230+
. If multiple beans remain after this filtering, regular executors are preferred over schedulers
231+
. If Spring's default `applicationTaskExecutor` is among multiple candidates, it is discarded to prefer application-defined beans
232+
. If multiple candidates still remain, Vaadin will throw an [classname]`IllegalStateException` with suggestions to resolve the conflict
233+
234+
There are several ways to customize the executor used by Vaadin in your Spring application.
235+
If you define a single [interfacename]`TaskExecutor` bean in your Spring application context, Vaadin will automatically use it.
236+
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`.
237+
238+
[.example]
239+
--
240+
241+
[source,java]
242+
----
243+
<source-info group="Single TaskExecutor Bean"></source-info>
244+
@Bean
245+
public TaskExecutor vaadinExecutor() {
246+
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
247+
executor.setCorePoolSize(10);
248+
executor.setMaxPoolSize(50);
249+
executor.setQueueCapacity(100);
250+
executor.setThreadNamePrefix("vaadin-custom-executor-");
251+
return executor;
252+
}
253+
----
254+
255+
[source,java]
256+
----
257+
<source-info group="Annotated TaskExecutor Bean"></source-info>
258+
@Bean
259+
@VaadinTaskExecutor
260+
public TaskExecutor vaadinSpecificExecutor() {
261+
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
262+
executor.setCorePoolSize(10);
263+
executor.setMaxPoolSize(50);
264+
executor.setQueueCapacity(100);
265+
executor.setThreadNamePrefix("vaadin-annotated-executor-");
266+
return executor;
267+
}
268+
----
269+
270+
[source,java]
271+
----
272+
<source-info group="Named TaskExecutor Bean"></source-info>
273+
@Bean("VaadinTaskExecutor")
274+
public TaskExecutor customVaadinExecutor() {
275+
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
276+
executor.setCorePoolSize(10);
277+
executor.setMaxPoolSize(50);
278+
executor.setQueueCapacity(100);
279+
executor.setThreadNamePrefix("vaadin-named-executor-");
280+
return executor;
281+
}
282+
----
283+
284+
--
285+
218286

219287
== Configure Spring MVC Applications
220288

0 commit comments

Comments
 (0)