From 86de754bb2f5c6f6451215e4f714d2899337d8a5 Mon Sep 17 00:00:00 2001 From: Andriy Dmytruk <80816836+andriy-dmytruk@users.noreply.github.com> Date: Fri, 31 May 2024 13:52:00 -0400 Subject: [PATCH] Support configuring minThreads and maxThreads (#722) --- .../micronaut/servlet/jetty/JettyFactory.java | 18 ++++-- .../jetty/JettyConfigurationSpec.groovy | 58 +++++++++++++++++++ .../servlet/tomcat/TomcatFactory.java | 16 +++++ .../servlet/undertow/UndertowFactory.java | 6 ++ .../servlet/http/ServletConfiguration.java | 19 ++++++ .../engine/MicronautServletConfiguration.java | 31 ++++++++++ 6 files changed, 142 insertions(+), 6 deletions(-) create mode 100644 http-server-jetty/src/test/groovy/io/micronaut/servlet/jetty/JettyConfigurationSpec.groovy diff --git a/http-server-jetty/src/main/java/io/micronaut/servlet/jetty/JettyFactory.java b/http-server-jetty/src/main/java/io/micronaut/servlet/jetty/JettyFactory.java index 6211ab827..2e14e30e9 100644 --- a/http-server-jetty/src/main/java/io/micronaut/servlet/jetty/JettyFactory.java +++ b/http-server-jetty/src/main/java/io/micronaut/servlet/jetty/JettyFactory.java @@ -365,17 +365,23 @@ private void applyAdditionalPorts(Server server, ServerConnector serverConnector * @return The server */ protected @NonNull Server newServer(@NonNull ApplicationContext applicationContext, @NonNull MicronautServletConfiguration configuration) { - Server server; + QueuedThreadPool threadPool; + if (configuration.getMaxThreads() != null) { + if (configuration.getMinThreads() != null) { + threadPool = new QueuedThreadPool(configuration.getMaxThreads(), configuration.getMinThreads()); + } else { + threadPool = new QueuedThreadPool(configuration.getMaxThreads()); + } + } else { + threadPool = new QueuedThreadPool(); + } + if (configuration.isEnableVirtualThreads() && LoomSupport.isSupported()) { - QueuedThreadPool threadPool = new QueuedThreadPool(); threadPool.setVirtualThreadsExecutor( applicationContext.getBean(ExecutorService.class, Qualifiers.byName(TaskExecutors.BLOCKING)) ); - server = new Server(threadPool); - } else { - server = new Server(); } - return server; + return new Server(threadPool); } /** diff --git a/http-server-jetty/src/test/groovy/io/micronaut/servlet/jetty/JettyConfigurationSpec.groovy b/http-server-jetty/src/test/groovy/io/micronaut/servlet/jetty/JettyConfigurationSpec.groovy new file mode 100644 index 000000000..e5e231511 --- /dev/null +++ b/http-server-jetty/src/test/groovy/io/micronaut/servlet/jetty/JettyConfigurationSpec.groovy @@ -0,0 +1,58 @@ +package io.micronaut.servlet.jetty + +import io.micronaut.context.annotation.Property +import io.micronaut.http.HttpRequest +import io.micronaut.http.MediaType +import io.micronaut.http.annotation.Controller +import io.micronaut.http.annotation.Get +import io.micronaut.http.annotation.Produces +import io.micronaut.http.annotation.QueryValue +import io.micronaut.http.client.HttpClient +import io.micronaut.http.client.annotation.Client +import io.micronaut.servlet.engine.MicronautServletConfiguration +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import jakarta.inject.Inject +import org.eclipse.jetty.server.Server +import org.eclipse.jetty.util.Jetty +import spock.lang.Specification + +@MicronautTest +@Property(name = "spec.name", value = SPEC_NAME) +@Property(name = "micronaut.servlet.minThreads", value = "11") +@Property(name = "micronaut.servlet.maxThreads", value = "11") +class JettyConfigurationSpec extends Specification { + + static final String SPEC_NAME = "JettyConfigurationSpec" + + @Inject + Server jetty + + @Inject + @Client("/configTest") + HttpClient client + + void "configuring thread pool is supported"() { + when: + var threadPool = jetty.threadPool + + then: + threadPool.threads == 11 + + when: + def request = HttpRequest.GET("/") + String response = client.toBlocking().retrieve(request) + + then: + response == "OK" + } + + @Controller("/configTest") + static class TestController { + + @Get + @Produces(MediaType.TEXT_PLAIN) + String index() { + "OK" + } + } +} diff --git a/http-server-tomcat/src/main/java/io/micronaut/servlet/tomcat/TomcatFactory.java b/http-server-tomcat/src/main/java/io/micronaut/servlet/tomcat/TomcatFactory.java index 89c77f31e..981917f50 100644 --- a/http-server-tomcat/src/main/java/io/micronaut/servlet/tomcat/TomcatFactory.java +++ b/http-server-tomcat/src/main/java/io/micronaut/servlet/tomcat/TomcatFactory.java @@ -47,6 +47,7 @@ import org.apache.catalina.Context; import org.apache.catalina.connector.Connector; import org.apache.catalina.core.ContainerBase; +import org.apache.catalina.core.StandardThreadExecutor; import org.apache.catalina.startup.Tomcat; import org.apache.coyote.ProtocolHandler; import org.apache.coyote.http2.Http2Protocol; @@ -124,6 +125,21 @@ protected Tomcat tomcatServer( configuration.setAsyncFileServingEnabled(false); Tomcat tomcat = newTomcat(); + if (configuration.getMaxThreads() != null) { + StandardThreadExecutor executor = new StandardThreadExecutor(); + executor.setName("tomcatThreadPool"); + executor.setMaxThreads(configuration.getMaxThreads()); + if (configuration.getMinThreads() != null) { + executor.setMinSpareThreads(configuration.getMinThreads()); + } + tomcat.getService().addExecutor(executor); + if (connector != null) { + connector.getProtocolHandler().setExecutor(executor); + } + if (httpsConnector != null) { + httpsConnector.getProtocolHandler().setExecutor(executor); + } + } final Context context = newTomcatContext(tomcat); configureServletInitializer(context, servletInitializers); diff --git a/http-server-undertow/src/main/java/io/micronaut/servlet/undertow/UndertowFactory.java b/http-server-undertow/src/main/java/io/micronaut/servlet/undertow/UndertowFactory.java index 0727ca403..3576ef784 100644 --- a/http-server-undertow/src/main/java/io/micronaut/servlet/undertow/UndertowFactory.java +++ b/http-server-undertow/src/main/java/io/micronaut/servlet/undertow/UndertowFactory.java @@ -163,6 +163,12 @@ protected Undertow.Builder undertowBuilder(DeploymentInfo deploymentInfo, Micron applyAdditionalPorts(builder, host, port, null); } + if (servletConfiguration.getMaxThreads() != null) { + builder.setServerOption(Options.WORKER_TASK_MAX_THREADS, servletConfiguration.getMaxThreads()); + if (servletConfiguration.getMinThreads() != null) { + builder.setServerOption(Options.WORKER_TASK_CORE_THREADS, servletConfiguration.getMinThreads()); + } + } Map serverOptions = configuration.getServerOptions(); serverOptions.forEach((key, value) -> { Object opt = ReflectionUtils.findDeclaredField(UndertowOptions.class, key) diff --git a/servlet-core/src/main/java/io/micronaut/servlet/http/ServletConfiguration.java b/servlet-core/src/main/java/io/micronaut/servlet/http/ServletConfiguration.java index bd6bb1a74..c9a59471d 100644 --- a/servlet-core/src/main/java/io/micronaut/servlet/http/ServletConfiguration.java +++ b/servlet-core/src/main/java/io/micronaut/servlet/http/ServletConfiguration.java @@ -50,4 +50,23 @@ default boolean isAsyncSupported() { default boolean isEnableVirtualThreads() { return true; } + + /** + * Get the minimum number of threads in the created thread pool. + * + * @return The minimum number of threads + */ + default Integer getMinThreads() { + return null; + } + + /** + * Get the maximum number of threads in the created thread pool. + * + * @return The maximum number of threads + */ + default Integer getMaxThreads() { + return null; + } + } diff --git a/servlet-engine/src/main/java/io/micronaut/servlet/engine/MicronautServletConfiguration.java b/servlet-engine/src/main/java/io/micronaut/servlet/engine/MicronautServletConfiguration.java index 407ef99ec..e11c5594c 100644 --- a/servlet-engine/src/main/java/io/micronaut/servlet/engine/MicronautServletConfiguration.java +++ b/servlet-engine/src/main/java/io/micronaut/servlet/engine/MicronautServletConfiguration.java @@ -51,6 +51,9 @@ public class MicronautServletConfiguration implements Named, ServletConfiguratio private boolean asyncSupported = true; private boolean enableVirtualThreads = true; + private Integer minThreads; + private Integer maxThreads; + /** * Default constructor. @@ -151,4 +154,32 @@ public void setAsyncFileServingEnabled(boolean enabled) { public boolean isAsyncFileServingEnabled() { return asyncSupported && asyncFileServingEnabled; } + + @Override + public Integer getMinThreads() { + return minThreads; + } + + /** + * Specify the minimum number of threads in the created thread pool. + * + * @param minThreads The minimum number of threads + */ + public void setMinThreads(Integer minThreads) { + this.minThreads = minThreads; + } + + @Override + public Integer getMaxThreads() { + return maxThreads; + } + + /** + * Specify the maximum number of threads in the created thread pool. + * + * @param maxThreads The maximum number of threads + */ + public void setMaxThreads(Integer maxThreads) { + this.maxThreads = maxThreads; + } }