Skip to content

Commit 86426d8

Browse files
authored
feat!: Use virtual threads for the default service executor (#22782)
Fixes #22780
1 parent f95aed8 commit 86426d8

File tree

2 files changed

+18
-63
lines changed

2 files changed

+18
-63
lines changed

flow-server/src/main/java/com/vaadin/flow/server/VaadinService.java

Lines changed: 14 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.io.OutputStreamWriter;
2323
import java.io.PrintWriter;
2424
import java.io.Serializable;
25+
import java.lang.Thread.Builder.OfVirtual;
2526
import java.lang.reflect.Constructor;
2627
import java.net.URL;
2728
import java.nio.charset.StandardCharsets;
@@ -42,12 +43,10 @@
4243
import java.util.concurrent.CopyOnWriteArrayList;
4344
import java.util.concurrent.Executor;
4445
import java.util.concurrent.ExecutorService;
46+
import java.util.concurrent.Executors;
4547
import java.util.concurrent.Future;
46-
import java.util.concurrent.LinkedBlockingQueue;
4748
import java.util.concurrent.ThreadFactory;
48-
import java.util.concurrent.ThreadPoolExecutor;
4949
import java.util.concurrent.TimeUnit;
50-
import java.util.concurrent.atomic.AtomicInteger;
5150
import java.util.concurrent.locks.Lock;
5251
import java.util.concurrent.locks.ReentrantLock;
5352
import java.util.stream.Collectors;
@@ -609,13 +608,8 @@ public Instantiator getInstantiator() {
609608
}
610609

611610
/**
612-
* Creates a default executor instance to use with this service.
613-
* <p>
614-
* This default implementation creates a thread pool executor with a custom
615-
* thread factory to generate daemon threads. It uses a core pool size of 8,
616-
* an unbounded maximum pool size, and a keep-alive time of 60 seconds for
617-
* idle threads. The thread pool grows dynamically as required, and idle
618-
* core threads are allowed to time out.
611+
* Creates a default executor instance to use with this service. This
612+
* default implementation creates a virtual tread executor.
619613
* <p>
620614
* A custom {@link VaadinService} implementation can override this method to
621615
* provide its own ad-hoc executor tailored to specific environments like
@@ -635,42 +629,17 @@ public Instantiator getInstantiator() {
635629
*/
636630
protected Executor createDefaultExecutor() {
637631
this.defaultExecutorInUse = true;
638-
int corePoolSize = 8;
639-
int keepAliveTimeSec = 60;
640-
641-
class VaadinThreadFactory implements ThreadFactory {
642-
private final AtomicInteger threadNumber = new AtomicInteger(0);
632+
ThreadFactory namedVirtualThreadFactory = defaultExecutorFactory()
633+
.factory();
634+
return Executors.newThreadPerTaskExecutor(namedVirtualThreadFactory);
635+
}
643636

644-
@Override
645-
public Thread newThread(Runnable runnable) {
646-
int threadNumber = this.threadNumber.incrementAndGet();
647-
if (threadNumber == 1) {
648-
getLogger().info(
649-
"The application is using Vaadin's default ThreadPoolExecutor "
650-
+ "(pool size = {}, keep alive time = {} seconds). "
651-
+ "A custom executor with an appropriate thread pool "
652-
+ "can be provided registering a {}.",
653-
corePoolSize, keepAliveTimeSec,
654-
VaadinServiceInitListener.class.getSimpleName());
655-
}
656-
Thread thread = new Thread(runnable,
657-
"VaadinTaskExecutor-thread-" + threadNumber);
658-
// Thread marked as daemon to prevent task execution to block
659-
// JVM shutdown
660-
thread.setDaemon(true);
661-
thread.setPriority(Thread.NORM_PRIORITY);
662-
return thread;
663-
}
664-
}
665-
// Defaults taken from Spring Boot configuration
666-
// org.springframework.boot.autoconfigure.task.TaskExecutionProperties.Pool
667-
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
668-
corePoolSize, Integer.MAX_VALUE, keepAliveTimeSec,
669-
TimeUnit.SECONDS, new LinkedBlockingQueue<>(),
670-
new VaadinThreadFactory());
671-
// Enables dynamic growing and shrinking of the pool.
672-
threadPoolExecutor.allowCoreThreadTimeOut(true);
673-
return threadPoolExecutor;
637+
/*
638+
* Package private to allow overriding with an uncaught exception handler in
639+
* tests
640+
*/
641+
OfVirtual defaultExecutorFactory() {
642+
return Thread.ofVirtual().name("VaadinTaskExecutor-thread-", 1);
674643
}
675644

676645
/**

flow-server/src/test/java/com/vaadin/flow/server/MockVaadinServletService.java

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,10 @@
1717

1818
import jakarta.servlet.ServletException;
1919

20+
import java.lang.Thread.Builder.OfVirtual;
2021
import java.lang.reflect.Field;
2122
import java.util.Collections;
2223
import java.util.List;
23-
import java.util.concurrent.Executor;
24-
import java.util.concurrent.ThreadFactory;
25-
import java.util.concurrent.ThreadPoolExecutor;
26-
import java.util.concurrent.TimeUnit;
2724

2825
import org.mockito.Mockito;
2926
import org.slf4j.LoggerFactory;
@@ -181,23 +178,12 @@ private void resetSignalEnvironment() {
181178
}
182179

183180
@Override
184-
protected Executor createDefaultExecutor() {
185-
Executor executor = super.createDefaultExecutor();
186-
if (executor instanceof ThreadPoolExecutor threadPoolExecutor) {
187-
threadPoolExecutor.setCorePoolSize(0);
188-
threadPoolExecutor.setMaximumPoolSize(4);
189-
threadPoolExecutor.setKeepAliveTime(10, TimeUnit.SECONDS);
190-
ThreadFactory threadFactory = threadPoolExecutor.getThreadFactory();
191-
threadPoolExecutor.setThreadFactory(r -> {
192-
Thread thread = threadFactory.newThread(r);
193-
thread.setUncaughtExceptionHandler((t, e) -> {
181+
OfVirtual defaultExecutorFactory() {
182+
return super.defaultExecutorFactory()
183+
.uncaughtExceptionHandler((t, e) -> {
194184
LoggerFactory.getLogger(getClass()).error(
195185
"An uncaught exception occurred in thread {}",
196186
t.getName(), e);
197187
});
198-
return thread;
199-
});
200-
}
201-
return executor;
202188
}
203189
}

0 commit comments

Comments
 (0)