Add property for max queue size for Tomcat

Co-authored-by: Ahmed A. Hussein <ahmedhussein411@gmail.com>

Closes gh-36087
This commit is contained in:
Moritz Halbritter 2023-09-06 16:30:37 +02:00 committed by Moritz Halbritter
parent 98609e875d
commit 91d187ca38
5 changed files with 68 additions and 19 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -905,6 +905,11 @@ public class ServerProperties {
*/
private int minSpare = 10;
/**
* Maximum capacity of the thread pool's backing queue.
*/
private int maxQueueCapacity = 2147483647;
public int getMax() {
return this.max;
}
@ -921,6 +926,14 @@ public class ServerProperties {
this.minSpare = minSpare;
}
public int getMaxQueueCapacity() {
return this.maxQueueCapacity;
}
public void setMaxQueueCapacity(int maxQueueCapacity) {
this.maxQueueCapacity = maxQueueCapacity;
}
}
/**

View File

@ -21,7 +21,10 @@ import java.util.List;
import java.util.function.ObjIntConsumer;
import java.util.stream.Collectors;
import javax.management.ObjectName;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.core.StandardThreadExecutor;
import org.apache.catalina.valves.AccessLogValve;
import org.apache.catalina.valves.ErrorReportValve;
import org.apache.catalina.valves.RemoteIpValve;
@ -36,6 +39,7 @@ import org.springframework.boot.autoconfigure.web.ErrorProperties.IncludeAttribu
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat.Accesslog;
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat.Remoteip;
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat.Threads;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory;
@ -94,13 +98,7 @@ public class TomcatWebServerFactoryCustomizer
.as(Long::intValue)
.to(factory::setBackgroundProcessorDelay);
customizeRemoteIpValve(factory);
ServerProperties.Tomcat.Threads threadProperties = properties.getThreads();
map.from(threadProperties::getMax)
.when(this::isPositive)
.to((maxThreads) -> customizeMaxThreads(factory, threadProperties.getMax()));
map.from(threadProperties::getMinSpare)
.when(this::isPositive)
.to((minSpareThreads) -> customizeMinThreads(factory, minSpareThreads));
configureExecutor(factory, properties.getThreads());
map.from(this.serverProperties.getMaxHttpRequestHeaderSize())
.asInt(DataSize::toBytes)
.when(this::isPositive)
@ -148,6 +146,19 @@ public class TomcatWebServerFactoryCustomizer
customizeErrorReportValve(this.serverProperties.getError(), factory);
}
private void configureExecutor(ConfigurableTomcatWebServerFactory factory, Threads threadProperties) {
factory.addProtocolHandlerCustomizers((handler) -> {
StandardThreadExecutor executor = new StandardThreadExecutor();
executor.setMinSpareThreads(threadProperties.getMinSpare());
executor.setMaxThreads(threadProperties.getMax());
executor.setMaxQueueSize(threadProperties.getMaxQueueCapacity());
if (handler instanceof AbstractProtocol<?> protocol) {
executor.setNamePrefix(ObjectName.unquote(protocol.getName()) + "-exec-");
}
handler.setExecutor(executor);
});
}
private boolean isPositive(int value) {
return value > 0;
}
@ -252,16 +263,6 @@ public class TomcatWebServerFactoryCustomizer
return this.serverProperties.getForwardHeadersStrategy() == ServerProperties.ForwardHeadersStrategy.NATIVE;
}
@SuppressWarnings("rawtypes")
private void customizeMaxThreads(ConfigurableTomcatWebServerFactory factory, int maxThreads) {
customizeHandler(factory, maxThreads, AbstractProtocol.class, AbstractProtocol::setMaxThreads);
}
@SuppressWarnings("rawtypes")
private void customizeMinThreads(ConfigurableTomcatWebServerFactory factory, int minSpareThreads) {
customizeHandler(factory, minSpareThreads, AbstractProtocol.class, AbstractProtocol::setMinSpareThreads);
}
@SuppressWarnings("rawtypes")
private void customizeMaxHttpRequestHeaderSize(ConfigurableTomcatWebServerFactory factory,
int maxHttpRequestHeaderSize) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,10 +17,12 @@
package org.springframework.boot.autoconfigure.web.embedded;
import java.util.Locale;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import org.apache.catalina.Context;
import org.apache.catalina.Valve;
import org.apache.catalina.core.StandardThreadExecutor;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.valves.AccessLogValve;
import org.apache.catalina.valves.ErrorReportValve;
@ -58,6 +60,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Rafiullah Hamedy
* @author Victor Mandujano
* @author Parviz Rozikov
* @author Moritz Halbritter
*/
class TomcatWebServerFactoryCustomizerTests {
@ -564,6 +567,20 @@ class TomcatWebServerFactoryCustomizerTests {
server.stop();
}
@Test
void configureExecutor() {
bind("server.tomcat.threads.max=10", "server.tomcat.threads.min-spare=2",
"server.tomcat.threads.max-queue-capacity=20");
customizeAndRunServer((server) -> {
Executor executor = server.getTomcat().getConnector().getProtocolHandler().getExecutor();
assertThat(executor).isInstanceOf(StandardThreadExecutor.class);
StandardThreadExecutor standardThreadExecutor = (StandardThreadExecutor) executor;
assertThat(standardThreadExecutor.getMaxThreads()).isEqualTo(10);
assertThat(standardThreadExecutor.getMinSpareThreads()).isEqualTo(2);
assertThat(standardThreadExecutor.getMaxQueueSize()).isEqualTo(20);
});
}
private void bind(String... inlinedProperties) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, inlinedProperties);
new Binder(ConfigurationPropertySources.get(this.environment)).bind("server",

View File

@ -28,6 +28,7 @@ import java.util.Set;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Executor;
import org.apache.catalina.Host;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Valve;
@ -135,16 +136,24 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
registerConnectorExecutor(tomcat, connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
registerConnectorExecutor(tomcat, additionalConnector);
}
TomcatHttpHandlerAdapter servlet = new TomcatHttpHandlerAdapter(httpHandler);
prepareContext(tomcat.getHost(), servlet);
return getTomcatWebServer(tomcat);
}
private void registerConnectorExecutor(Tomcat tomcat, Connector connector) {
if (connector.getProtocolHandler().getExecutor() instanceof Executor executor) {
tomcat.getService().addExecutor(executor);
}
}
private void configureEngine(Engine engine) {
engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay);
for (Valve valve : this.engineValves) {

View File

@ -39,6 +39,7 @@ import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Executor;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
@ -209,15 +210,23 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
registerConnectorExecutor(tomcat, connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
registerConnectorExecutor(tomcat, additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
private void registerConnectorExecutor(Tomcat tomcat, Connector connector) {
if (connector.getProtocolHandler().getExecutor() instanceof Executor executor) {
tomcat.getService().addExecutor(executor);
}
}
private void configureEngine(Engine engine) {
engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay);
for (Valve valve : this.engineValves) {