41
41
import java .util .concurrent .CancellationException ;
42
42
import java .util .concurrent .ConcurrentHashMap ;
43
43
import java .util .concurrent .CopyOnWriteArrayList ;
44
+ import java .util .concurrent .Executor ;
45
+ import java .util .concurrent .ExecutorService ;
44
46
import java .util .concurrent .Future ;
47
+ import java .util .concurrent .LinkedBlockingQueue ;
48
+ import java .util .concurrent .ThreadFactory ;
49
+ import java .util .concurrent .ThreadPoolExecutor ;
45
50
import java .util .concurrent .TimeUnit ;
51
+ import java .util .concurrent .atomic .AtomicInteger ;
46
52
import java .util .concurrent .locks .Lock ;
47
53
import java .util .concurrent .locks .ReentrantLock ;
48
54
import java .util .stream .Collectors ;
@@ -184,6 +190,10 @@ public abstract class VaadinService implements Serializable {
184
190
185
191
private Instantiator instantiator ;
186
192
193
+ private Executor executor ;
194
+
195
+ private boolean defaultExecutorInUse ;
196
+
187
197
private VaadinContext vaadinContext ;
188
198
189
199
private Iterable <VaadinRequestInterceptor > vaadinRequestInterceptors ;
@@ -267,6 +277,9 @@ public void init() throws ServiceException {
267
277
instantiator .getServiceInitListeners ()
268
278
.forEach (listener -> listener .serviceInit (event ));
269
279
280
+ this .executor = event .getExecutor ()
281
+ .orElseGet (this ::createDefaultExecutor );
282
+
270
283
event .getAddedRequestHandlers ().forEach (handlers ::add );
271
284
272
285
Collections .reverse (handlers );
@@ -293,6 +306,18 @@ public void init() throws ServiceException {
293
306
.collect (Collectors .toList ());
294
307
});
295
308
309
+ if (this .executor == null ) {
310
+ throw new ServiceException (
311
+ "Unable to create the default Executor for "
312
+ + getClass ().getName ()
313
+ + ". This is most likely a bug in a custom VaadinService implementation "
314
+ + "that overrides the createDefaultExecutor() method "
315
+ + "but returns a null Executor instance. "
316
+ + "As a workaround, you can register a "
317
+ + VaadinServiceInitListener .class .getSimpleName ()
318
+ + " providing a custom Executor instance." );
319
+ }
320
+
296
321
DeploymentConfiguration configuration = getDeploymentConfiguration ();
297
322
if (!configuration .isProductionMode ()) {
298
323
Logger logger = getLogger ();
@@ -503,6 +528,98 @@ public Instantiator getInstantiator() {
503
528
return instantiator ;
504
529
}
505
530
531
+ /**
532
+ * Creates a default executor instance to use with this service.
533
+ * <p>
534
+ * This default implementation creates a thread pool executor with a custom
535
+ * thread factory to generate daemon threads. It uses a core pool size of 8,
536
+ * an unbounded maximum pool size, and a keep-alive time of 60 seconds for
537
+ * idle threads. The thread pool grows dynamically as required, and idle
538
+ * core threads are allowed to time out.
539
+ * <p>
540
+ * A custom {@link VaadinService} implementation can override this method to
541
+ * provide its own ad-hoc executor tailored to specific environments like
542
+ * CDI or Spring.
543
+ * <p>
544
+ * Implementors should never return {@literal null}; if an executor instance
545
+ * cannot be provided, the method should call
546
+ * {@code super.createDefaultExecutor()}.
547
+ * <p>
548
+ * The application can provide a more appropriate executor implementation
549
+ * through a {@link VaadinServiceInitListener} and calling
550
+ * {@link ServiceInitEvent#setExecutor(Executor)}.
551
+ *
552
+ * @return a default executor instance to use, never {@literal null}.
553
+ * @see VaadinServiceInitListener
554
+ * @see ServiceInitEvent#setExecutor(Executor)
555
+ */
556
+ protected Executor createDefaultExecutor () {
557
+ this .defaultExecutorInUse = true ;
558
+ int corePoolSize = 8 ;
559
+ int keepAliveTimeSec = 60 ;
560
+
561
+ class VaadinThreadFactory implements ThreadFactory {
562
+ private final AtomicInteger threadNumber = new AtomicInteger (0 );
563
+
564
+ @ Override
565
+ public Thread newThread (Runnable runnable ) {
566
+ int threadNumber = this .threadNumber .incrementAndGet ();
567
+ if (threadNumber == 1 ) {
568
+ getLogger ().info (
569
+ "The application is using Vaadin's default ThreadPoolExecutor "
570
+ + "(pool size = {}, keep alive time = {} seconds). "
571
+ + "A custom executor with an appropriate thread pool "
572
+ + "can be provided registering a {}." ,
573
+ corePoolSize , keepAliveTimeSec ,
574
+ VaadinServiceInitListener .class .getSimpleName ());
575
+ }
576
+ Thread thread = new Thread (runnable ,
577
+ "VaadinTaskExecutor-thread-" + threadNumber );
578
+ // Thread marked as daemon to prevent task execution to block
579
+ // JVM shutdown
580
+ thread .setDaemon (true );
581
+ thread .setPriority (Thread .NORM_PRIORITY );
582
+ return thread ;
583
+ }
584
+ }
585
+ // Defaults taken from Spring Boot configuration
586
+ // org.springframework.boot.autoconfigure.task.TaskExecutionProperties.Pool
587
+ ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor (
588
+ corePoolSize , Integer .MAX_VALUE , keepAliveTimeSec ,
589
+ TimeUnit .SECONDS , new LinkedBlockingQueue <>(),
590
+ new VaadinThreadFactory ());
591
+ // Enables dynamic growing and shrinking of the pool.
592
+ threadPoolExecutor .allowCoreThreadTimeOut (true );
593
+ return threadPoolExecutor ;
594
+ }
595
+
596
+ /**
597
+ * Gets the executor instance used by Vaadin for managing concurrent tasks.
598
+ * <p>
599
+ * By default, a thread pool executor with a custom with core pool size of
600
+ * 8, an unbounded maximum pool size, and a keep-alive time of 60 seconds
601
+ * for idle threads is provided. The thread pool grows dynamically as
602
+ * required, and idle core threads are allowed to time out.
603
+ * <p>
604
+ * {@link VaadinService} implementations for specific environments like CDI
605
+ * or Spring might provide their own ad-hoc Executors tailored to those
606
+ * environments.
607
+ * <p>
608
+ * A custom executor can be configured by registering a
609
+ * {@link VaadinServiceInitListener} and providing the executor instance to
610
+ * the {@link ServiceInitEvent}.
611
+ * <p>
612
+ * A Vaadin application can also benefit from this executor to submit
613
+ * asynchronous tasks.
614
+ *
615
+ * @return the Executor instance, never {@literal null}.
616
+ * @see VaadinServiceInitListener
617
+ * @see ServiceInitEvent#setExecutor(Executor)
618
+ */
619
+ public Executor getExecutor () {
620
+ return executor ;
621
+ }
622
+
506
623
/**
507
624
* Gets the class loader to use for loading classes loaded by name, e.g.
508
625
* custom UI classes. This is by default the class loader that was used to
@@ -2216,6 +2333,10 @@ public Registration addServiceDestroyListener(
2216
2333
*/
2217
2334
public void destroy () {
2218
2335
ServiceDestroyEvent event = new ServiceDestroyEvent (this );
2336
+ if (defaultExecutorInUse && executor instanceof ExecutorService cast ) {
2337
+ cast .shutdownNow ();
2338
+ this .executor = null ;
2339
+ }
2219
2340
RuntimeException exception = null ;
2220
2341
for (ServiceDestroyListener listener : serviceDestroyListeners ) {
2221
2342
try {
0 commit comments