Skip to content

Commit 91d187c

Browse files
committed
Add property for max queue size for Tomcat
Co-authored-by: Ahmed A. Hussein <[email protected]> Closes gh-36087
1 parent 98609e8 commit 91d187c

File tree

5 files changed

+68
-19
lines changed

5 files changed

+68
-19
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -905,6 +905,11 @@ public static class Threads {
905905
*/
906906
private int minSpare = 10;
907907

908+
/**
909+
* Maximum capacity of the thread pool's backing queue.
910+
*/
911+
private int maxQueueCapacity = 2147483647;
912+
908913
public int getMax() {
909914
return this.max;
910915
}
@@ -921,6 +926,14 @@ public void setMinSpare(int minSpare) {
921926
this.minSpare = minSpare;
922927
}
923928

929+
public int getMaxQueueCapacity() {
930+
return this.maxQueueCapacity;
931+
}
932+
933+
public void setMaxQueueCapacity(int maxQueueCapacity) {
934+
this.maxQueueCapacity = maxQueueCapacity;
935+
}
936+
924937
}
925938

926939
/**

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@
2121
import java.util.function.ObjIntConsumer;
2222
import java.util.stream.Collectors;
2323

24+
import javax.management.ObjectName;
25+
2426
import org.apache.catalina.Lifecycle;
27+
import org.apache.catalina.core.StandardThreadExecutor;
2528
import org.apache.catalina.valves.AccessLogValve;
2629
import org.apache.catalina.valves.ErrorReportValve;
2730
import org.apache.catalina.valves.RemoteIpValve;
@@ -36,6 +39,7 @@
3639
import org.springframework.boot.autoconfigure.web.ServerProperties;
3740
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat.Accesslog;
3841
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat.Remoteip;
42+
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat.Threads;
3943
import org.springframework.boot.cloud.CloudPlatform;
4044
import org.springframework.boot.context.properties.PropertyMapper;
4145
import org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory;
@@ -94,13 +98,7 @@ public void customize(ConfigurableTomcatWebServerFactory factory) {
9498
.as(Long::intValue)
9599
.to(factory::setBackgroundProcessorDelay);
96100
customizeRemoteIpValve(factory);
97-
ServerProperties.Tomcat.Threads threadProperties = properties.getThreads();
98-
map.from(threadProperties::getMax)
99-
.when(this::isPositive)
100-
.to((maxThreads) -> customizeMaxThreads(factory, threadProperties.getMax()));
101-
map.from(threadProperties::getMinSpare)
102-
.when(this::isPositive)
103-
.to((minSpareThreads) -> customizeMinThreads(factory, minSpareThreads));
101+
configureExecutor(factory, properties.getThreads());
104102
map.from(this.serverProperties.getMaxHttpRequestHeaderSize())
105103
.asInt(DataSize::toBytes)
106104
.when(this::isPositive)
@@ -148,6 +146,19 @@ public void customize(ConfigurableTomcatWebServerFactory factory) {
148146
customizeErrorReportValve(this.serverProperties.getError(), factory);
149147
}
150148

149+
private void configureExecutor(ConfigurableTomcatWebServerFactory factory, Threads threadProperties) {
150+
factory.addProtocolHandlerCustomizers((handler) -> {
151+
StandardThreadExecutor executor = new StandardThreadExecutor();
152+
executor.setMinSpareThreads(threadProperties.getMinSpare());
153+
executor.setMaxThreads(threadProperties.getMax());
154+
executor.setMaxQueueSize(threadProperties.getMaxQueueCapacity());
155+
if (handler instanceof AbstractProtocol<?> protocol) {
156+
executor.setNamePrefix(ObjectName.unquote(protocol.getName()) + "-exec-");
157+
}
158+
handler.setExecutor(executor);
159+
});
160+
}
161+
151162
private boolean isPositive(int value) {
152163
return value > 0;
153164
}
@@ -252,16 +263,6 @@ private boolean getOrDeduceUseForwardHeaders() {
252263
return this.serverProperties.getForwardHeadersStrategy() == ServerProperties.ForwardHeadersStrategy.NATIVE;
253264
}
254265

255-
@SuppressWarnings("rawtypes")
256-
private void customizeMaxThreads(ConfigurableTomcatWebServerFactory factory, int maxThreads) {
257-
customizeHandler(factory, maxThreads, AbstractProtocol.class, AbstractProtocol::setMaxThreads);
258-
}
259-
260-
@SuppressWarnings("rawtypes")
261-
private void customizeMinThreads(ConfigurableTomcatWebServerFactory factory, int minSpareThreads) {
262-
customizeHandler(factory, minSpareThreads, AbstractProtocol.class, AbstractProtocol::setMinSpareThreads);
263-
}
264-
265266
@SuppressWarnings("rawtypes")
266267
private void customizeMaxHttpRequestHeaderSize(ConfigurableTomcatWebServerFactory factory,
267268
int maxHttpRequestHeaderSize) {

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizerTests.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,10 +17,12 @@
1717
package org.springframework.boot.autoconfigure.web.embedded;
1818

1919
import java.util.Locale;
20+
import java.util.concurrent.Executor;
2021
import java.util.function.Consumer;
2122

2223
import org.apache.catalina.Context;
2324
import org.apache.catalina.Valve;
25+
import org.apache.catalina.core.StandardThreadExecutor;
2426
import org.apache.catalina.startup.Tomcat;
2527
import org.apache.catalina.valves.AccessLogValve;
2628
import org.apache.catalina.valves.ErrorReportValve;
@@ -58,6 +60,7 @@
5860
* @author Rafiullah Hamedy
5961
* @author Victor Mandujano
6062
* @author Parviz Rozikov
63+
* @author Moritz Halbritter
6164
*/
6265
class TomcatWebServerFactoryCustomizerTests {
6366

@@ -564,6 +567,20 @@ void ajpConnectorCanBeCustomized() {
564567
server.stop();
565568
}
566569

570+
@Test
571+
void configureExecutor() {
572+
bind("server.tomcat.threads.max=10", "server.tomcat.threads.min-spare=2",
573+
"server.tomcat.threads.max-queue-capacity=20");
574+
customizeAndRunServer((server) -> {
575+
Executor executor = server.getTomcat().getConnector().getProtocolHandler().getExecutor();
576+
assertThat(executor).isInstanceOf(StandardThreadExecutor.class);
577+
StandardThreadExecutor standardThreadExecutor = (StandardThreadExecutor) executor;
578+
assertThat(standardThreadExecutor.getMaxThreads()).isEqualTo(10);
579+
assertThat(standardThreadExecutor.getMinSpareThreads()).isEqualTo(2);
580+
assertThat(standardThreadExecutor.getMaxQueueSize()).isEqualTo(20);
581+
});
582+
}
583+
567584
private void bind(String... inlinedProperties) {
568585
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, inlinedProperties);
569586
new Binder(ConfigurationPropertySources.get(this.environment)).bind("server",

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import org.apache.catalina.Context;
3030
import org.apache.catalina.Engine;
31+
import org.apache.catalina.Executor;
3132
import org.apache.catalina.Host;
3233
import org.apache.catalina.LifecycleListener;
3334
import org.apache.catalina.Valve;
@@ -135,16 +136,24 @@ public WebServer getWebServer(HttpHandler httpHandler) {
135136
tomcat.getService().addConnector(connector);
136137
customizeConnector(connector);
137138
tomcat.setConnector(connector);
139+
registerConnectorExecutor(tomcat, connector);
138140
tomcat.getHost().setAutoDeploy(false);
139141
configureEngine(tomcat.getEngine());
140142
for (Connector additionalConnector : this.additionalTomcatConnectors) {
141143
tomcat.getService().addConnector(additionalConnector);
144+
registerConnectorExecutor(tomcat, additionalConnector);
142145
}
143146
TomcatHttpHandlerAdapter servlet = new TomcatHttpHandlerAdapter(httpHandler);
144147
prepareContext(tomcat.getHost(), servlet);
145148
return getTomcatWebServer(tomcat);
146149
}
147150

151+
private void registerConnectorExecutor(Tomcat tomcat, Connector connector) {
152+
if (connector.getProtocolHandler().getExecutor() instanceof Executor executor) {
153+
tomcat.getService().addExecutor(executor);
154+
}
155+
}
156+
148157
private void configureEngine(Engine engine) {
149158
engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay);
150159
for (Valve valve : this.engineValves) {

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import jakarta.servlet.http.HttpServletRequest;
4040
import org.apache.catalina.Context;
4141
import org.apache.catalina.Engine;
42+
import org.apache.catalina.Executor;
4243
import org.apache.catalina.Host;
4344
import org.apache.catalina.Lifecycle;
4445
import org.apache.catalina.LifecycleEvent;
@@ -209,15 +210,23 @@ public WebServer getWebServer(ServletContextInitializer... initializers) {
209210
tomcat.getService().addConnector(connector);
210211
customizeConnector(connector);
211212
tomcat.setConnector(connector);
213+
registerConnectorExecutor(tomcat, connector);
212214
tomcat.getHost().setAutoDeploy(false);
213215
configureEngine(tomcat.getEngine());
214216
for (Connector additionalConnector : this.additionalTomcatConnectors) {
215217
tomcat.getService().addConnector(additionalConnector);
218+
registerConnectorExecutor(tomcat, additionalConnector);
216219
}
217220
prepareContext(tomcat.getHost(), initializers);
218221
return getTomcatWebServer(tomcat);
219222
}
220223

224+
private void registerConnectorExecutor(Tomcat tomcat, Connector connector) {
225+
if (connector.getProtocolHandler().getExecutor() instanceof Executor executor) {
226+
tomcat.getService().addExecutor(executor);
227+
}
228+
}
229+
221230
private void configureEngine(Engine engine) {
222231
engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay);
223232
for (Valve valve : this.engineValves) {

0 commit comments

Comments
 (0)