diff --git a/common/src/main/java/stirling/software/common/service/JobExecutorService.java b/common/src/main/java/stirling/software/common/service/JobExecutorService.java index 0806e4abb..ba185628c 100644 --- a/common/src/main/java/stirling/software/common/service/JobExecutorService.java +++ b/common/src/main/java/stirling/software/common/service/JobExecutorService.java @@ -5,7 +5,6 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Supplier; @@ -24,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; import stirling.software.common.controller.WebSocketProgressController; import stirling.software.common.model.job.JobProgress; import stirling.software.common.model.job.JobResponse; +import stirling.software.common.util.ExecutorFactory; /** Service for executing jobs asynchronously or synchronously */ @Service @@ -36,7 +36,7 @@ public class JobExecutorService { private final HttpServletRequest request; private final ResourceMonitor resourceMonitor; private final JobQueue jobQueue; - private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); + private final ExecutorService executor = ExecutorFactory.newVirtualOrCachedThreadExecutor(); private final long effectiveTimeoutMs; public JobExecutorService( diff --git a/common/src/main/java/stirling/software/common/service/JobQueue.java b/common/src/main/java/stirling/software/common/service/JobQueue.java index a84056fd0..2ab6354fe 100644 --- a/common/src/main/java/stirling/software/common/service/JobQueue.java +++ b/common/src/main/java/stirling/software/common/service/JobQueue.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import stirling.software.common.controller.WebSocketProgressController; import stirling.software.common.model.job.JobProgress; +import stirling.software.common.util.ExecutorFactory; /** * Manages a queue of jobs with dynamic sizing based on system resources. Used when system resources @@ -46,18 +47,8 @@ public class JobQueue { private BlockingQueue jobQueue; private final Map jobMap = new ConcurrentHashMap<>(); private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); - private final ExecutorService jobExecutor; - - // Initialize executor based on Java version - { - ExecutorService executor; - try { - executor = Executors.newVirtualThreadPerTaskExecutor(); - } catch (NoSuchMethodError e) { - executor = Executors.newCachedThreadPool(); - } - jobExecutor = executor; - } + private final ExecutorService jobExecutor = ExecutorFactory.newVirtualOrCachedThreadExecutor(); + private boolean shuttingDown = false; @Getter private int rejectedJobs = 0; diff --git a/common/src/main/java/stirling/software/common/util/ExecutorFactory.java b/common/src/main/java/stirling/software/common/util/ExecutorFactory.java new file mode 100644 index 000000000..13cca386e --- /dev/null +++ b/common/src/main/java/stirling/software/common/util/ExecutorFactory.java @@ -0,0 +1,31 @@ +package stirling.software.common.util; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ExecutorFactory { + + /** + * Creates an ExecutorService using virtual threads if available (Java 21+), or falls back to a + * cached thread pool on older Java versions. + */ + public static ExecutorService newVirtualOrCachedThreadExecutor() { + try { + ExecutorService executor = + (ExecutorService) + Executors.class + .getMethod("newVirtualThreadPerTaskExecutor") + .invoke(null); + return executor; + } catch (NoSuchMethodException e) { + log.debug("Virtual threads not available; falling back to cached thread pool."); + } catch (Exception e) { + log.debug("Error initializing virtual thread executor: {}", e.getMessage(), e); + } + + return Executors.newCachedThreadPool(); + } +}