Commit f8173eb7 authored by Stephane Nicoll's avatar Stephane Nicoll

Merge pull request #19494 from bono007

* pr/19494:
  Polish "Add support for configuring Jetty's backing queue"
  Add support for configuring Jetty's backing queue

Closes gh-19494
parents a6fdbdcd b56c4f1a
......@@ -1031,15 +1031,21 @@ public class ServerProperties {
*/
private Integer selectors = -1;
/**
* Minimum number of threads.
*/
private int minThreads = 8;
/**
* Maximum number of threads.
*/
private Integer maxThreads = 200;
private int maxThreads = 200;
/**
* Minimum number of threads.
* Maximum capacity of the thread pool's backing queue. A default is computed
* based on the threading configuration.
*/
private Integer minThreads = 8;
private Integer maxQueueCapacity;
/**
* Maximum thread idle time.
......@@ -1090,22 +1096,30 @@ public class ServerProperties {
this.selectors = selectors;
}
public void setMinThreads(Integer minThreads) {
public void setMinThreads(int minThreads) {
this.minThreads = minThreads;
}
public Integer getMinThreads() {
public int getMinThreads() {
return this.minThreads;
}
public void setMaxThreads(Integer maxThreads) {
public void setMaxThreads(int maxThreads) {
this.maxThreads = maxThreads;
}
public Integer getMaxThreads() {
public int getMaxThreads() {
return this.maxThreads;
}
public void setMaxQueueCapacity(Integer maxQueueCapacity) {
this.maxQueueCapacity = maxQueueCapacity;
}
public Integer getMaxQueueCapacity() {
return this.maxQueueCapacity;
}
public void setThreadIdleTimeout(Duration threadIdleTimeout) {
this.threadIdleTimeout = threadIdleTimeout;
}
......
......@@ -18,7 +18,8 @@ package org.springframework.boot.autoconfigure.web.embedded;
import java.time.Duration;
import java.util.Arrays;
import java.util.function.Consumer;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.ConnectionFactory;
......@@ -30,6 +31,7 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ThreadPool;
......@@ -75,6 +77,7 @@ public class JettyWebServerFactoryCustomizer
ServerProperties properties = this.serverProperties;
ServerProperties.Jetty jettyProperties = properties.getJetty();
factory.setUseForwardHeaders(getOrDeduceUseForwardHeaders());
factory.setThreadPool(determineThreadPool(jettyProperties));
PropertyMapper propertyMapper = PropertyMapper.get();
propertyMapper.from(jettyProperties::getAcceptors).whenNonNull().to(factory::setAcceptors);
propertyMapper.from(jettyProperties::getSelectors).whenNonNull().to(factory::setSelectors);
......@@ -83,12 +86,6 @@ public class JettyWebServerFactoryCustomizer
.addServerCustomizers(new MaxHttpHeaderSizeCustomizer(maxHttpHeaderSize)));
propertyMapper.from(jettyProperties::getMaxHttpFormPostSize).asInt(DataSize::toBytes).when(this::isPositive)
.to((maxHttpFormPostSize) -> customizeMaxHttpFormPostSize(factory, maxHttpFormPostSize));
propertyMapper.from(jettyProperties::getMaxThreads).when(this::isPositive)
.to((maxThreads) -> customizeThreadPool(factory, (threadPool) -> threadPool.setMaxThreads(maxThreads)));
propertyMapper.from(jettyProperties::getMinThreads).when(this::isPositive)
.to((minThreads) -> customizeThreadPool(factory, (threadPool) -> threadPool.setMinThreads(minThreads)));
propertyMapper.from(jettyProperties::getThreadIdleTimeout).whenNonNull().asInt(Duration::toMillis).to(
(idleTimeout) -> customizeThreadPool(factory, (threadPool) -> threadPool.setIdleTimeout(idleTimeout)));
propertyMapper.from(properties::getConnectionTimeout).whenNonNull()
.to((connectionTimeout) -> customizeIdleTimeout(factory, connectionTimeout));
propertyMapper.from(jettyProperties::getConnectionIdleTimeout).whenNonNull()
......@@ -144,13 +141,25 @@ public class JettyWebServerFactoryCustomizer
});
}
private void customizeThreadPool(ConfigurableJettyWebServerFactory factory, Consumer<QueuedThreadPool> customizer) {
factory.addServerCustomizers((connector) -> {
ThreadPool threadPool = connector.getThreadPool();
if (threadPool instanceof QueuedThreadPool) {
customizer.accept((QueuedThreadPool) threadPool);
}
});
private ThreadPool determineThreadPool(ServerProperties.Jetty properties) {
BlockingQueue<Runnable> queue = determineBlockingQueue(properties.getMaxQueueCapacity());
int maxThreadCount = (properties.getMaxThreads() > 0) ? properties.getMaxThreads() : 200;
int minThreadCount = (properties.getMinThreads() > 0) ? properties.getMinThreads() : 8;
int threadIdleTimeout = (properties.getThreadIdleTimeout() != null)
? (int) properties.getThreadIdleTimeout().toMillis() : 60000;
return new QueuedThreadPool(maxThreadCount, minThreadCount, threadIdleTimeout, queue);
}
private BlockingQueue<Runnable> determineBlockingQueue(Integer maxQueueCapacity) {
if (maxQueueCapacity == null) {
return null;
}
if (maxQueueCapacity == 0) {
return new SynchronousQueue<>();
}
else {
return new BlockingArrayQueue<>(maxQueueCapacity);
}
}
private void customizeAccessLog(ConfigurableJettyWebServerFactory factory,
......
......@@ -237,6 +237,12 @@ class ServerPropertiesTests {
assertThat(this.properties.getJetty().getThreadIdleTimeout()).hasSeconds(10);
}
@Test
void testCustomizeJettyMaxQueueCapacity() {
bind("server.jetty.max-queue-capacity", "5150");
assertThat(this.properties.getJetty().getMaxQueueCapacity()).isEqualTo(5150);
}
@Test
void testCustomizeUndertowServerOption() {
bind("server.undertow.options.server.ALWAYS_SET_KEEP_ALIVE", "true");
......
......@@ -18,9 +18,12 @@ package org.springframework.boot.autoconfigure.web.embedded;
import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.stream.Collectors;
import org.eclipse.jetty.server.AbstractConnector;
......@@ -30,11 +33,15 @@ import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConfiguration.ConnectionFactory;
import org.eclipse.jetty.server.RequestLog;
import org.eclipse.jetty.server.RequestLogWriter;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties.Jetty;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
......@@ -43,6 +50,7 @@ import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
import org.springframework.boot.web.embedded.jetty.JettyWebServer;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.test.context.support.TestPropertySourceUtils;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
......@@ -135,7 +143,26 @@ class JettyWebServerFactoryCustomizerTests {
}
@Test
void maxThreadsCanBeCustomized() {
void threadPoolMatchesJettyDefaults() {
ThreadPool defaultThreadPool = new Server(0).getThreadPool();
ThreadPool configuredThreadPool = customizeAndGetServer().getServer().getThreadPool();
assertThat(defaultThreadPool).isInstanceOf(QueuedThreadPool.class);
assertThat(configuredThreadPool).isInstanceOf(QueuedThreadPool.class);
QueuedThreadPool defaultQueuedThreadPool = (QueuedThreadPool) defaultThreadPool;
QueuedThreadPool configuredQueuedThreadPool = (QueuedThreadPool) configuredThreadPool;
assertThat(configuredQueuedThreadPool.getMinThreads()).isEqualTo(defaultQueuedThreadPool.getMinThreads());
assertThat(configuredQueuedThreadPool.getMaxThreads()).isEqualTo(defaultQueuedThreadPool.getMaxThreads());
assertThat(configuredQueuedThreadPool.getIdleTimeout()).isEqualTo(defaultQueuedThreadPool.getIdleTimeout());
BlockingQueue<?> defaultQueue = getQueue(defaultThreadPool);
BlockingQueue<?> configuredQueue = getQueue(configuredThreadPool);
assertThat(defaultQueue).isInstanceOf(BlockingArrayQueue.class);
assertThat(configuredQueue).isInstanceOf(BlockingArrayQueue.class);
assertThat(((BlockingArrayQueue<?>) defaultQueue).getMaxCapacity())
.isEqualTo(((BlockingArrayQueue<?>) configuredQueue).getMaxCapacity());
}
@Test
void threadPoolMaxThreadsCanBeCustomized() {
bind("server.jetty.max-threads=100");
JettyWebServer server = customizeAndGetServer();
QueuedThreadPool threadPool = (QueuedThreadPool) server.getServer().getThreadPool();
......@@ -143,7 +170,7 @@ class JettyWebServerFactoryCustomizerTests {
}
@Test
void minThreadsCanBeCustomized() {
void threadPoolMinThreadsCanBeCustomized() {
bind("server.jetty.min-threads=100");
JettyWebServer server = customizeAndGetServer();
QueuedThreadPool threadPool = (QueuedThreadPool) server.getServer().getThreadPool();
......@@ -151,13 +178,65 @@ class JettyWebServerFactoryCustomizerTests {
}
@Test
void threadIdleTimeoutCanBeCustomized() {
void threadPoolIdleTimeoutCanBeCustomized() {
bind("server.jetty.thread-idle-timeout=100s");
JettyWebServer server = customizeAndGetServer();
QueuedThreadPool threadPool = (QueuedThreadPool) server.getServer().getThreadPool();
assertThat(threadPool.getIdleTimeout()).isEqualTo(100000);
}
@Test
void threadPoolWithMaxQueueCapacityEqualToZeroCreateSynchronousQueue() {
bind("server.jetty.max-queue-capacity=0");
JettyWebServer server = customizeAndGetServer();
ThreadPool threadPool = server.getServer().getThreadPool();
BlockingQueue<?> queue = getQueue(threadPool);
assertThat(queue).isInstanceOf(SynchronousQueue.class);
assertDefaultThreadPoolSettings(threadPool);
}
@Test
void threadPoolWithMaxQueueCapacityEqualToZeroCustomizesThreadPool() {
bind("server.jetty.max-queue-capacity=0", "server.jetty.min-threads=100", "server.jetty.max-threads=100",
"server.jetty.thread-idle-timeout=6s");
JettyWebServer server = customizeAndGetServer();
QueuedThreadPool threadPool = (QueuedThreadPool) server.getServer().getThreadPool();
assertThat(threadPool.getMinThreads()).isEqualTo(100);
assertThat(threadPool.getMaxThreads()).isEqualTo(100);
assertThat(threadPool.getIdleTimeout()).isEqualTo(Duration.ofSeconds(6).toMillis());
}
@Test
void threadPoolWithMaxQueueCapacityPositiveCreateBlockingArrayQueue() {
bind("server.jetty.max-queue-capacity=1234");
JettyWebServer server = customizeAndGetServer();
ThreadPool threadPool = server.getServer().getThreadPool();
BlockingQueue<?> queue = getQueue(threadPool);
assertThat(queue).isInstanceOf(BlockingArrayQueue.class);
assertThat(((BlockingArrayQueue<?>) queue).getMaxCapacity()).isEqualTo(1234);
assertDefaultThreadPoolSettings(threadPool);
}
@Test
void threadPoolWithMaxQueueCapacityPositiveCustomizesThreadPool() {
bind("server.jetty.max-queue-capacity=1234", "server.jetty.min-threads=10", "server.jetty.max-threads=150",
"server.jetty.thread-idle-timeout=3s");
JettyWebServer server = customizeAndGetServer();
QueuedThreadPool threadPool = (QueuedThreadPool) server.getServer().getThreadPool();
assertThat(threadPool.getMinThreads()).isEqualTo(10);
assertThat(threadPool.getMaxThreads()).isEqualTo(150);
assertThat(threadPool.getIdleTimeout()).isEqualTo(Duration.ofSeconds(3).toMillis());
}
private void assertDefaultThreadPoolSettings(ThreadPool threadPool) {
assertThat(threadPool).isInstanceOf(QueuedThreadPool.class);
QueuedThreadPool queuedThreadPool = (QueuedThreadPool) threadPool;
Jetty defaultProperties = new Jetty();
assertThat(queuedThreadPool.getMinThreads()).isEqualTo(defaultProperties.getMinThreads());
assertThat(queuedThreadPool.getMaxThreads()).isEqualTo(defaultProperties.getMaxThreads());
assertThat(queuedThreadPool.getIdleTimeout()).isEqualTo(defaultProperties.getThreadIdleTimeout().toMillis());
}
private CustomRequestLog getRequestLog(JettyWebServer server) {
RequestLog requestLog = server.getServer().getRequestLog();
assertThat(requestLog).isInstanceOf(CustomRequestLog.class);
......@@ -236,6 +315,10 @@ class JettyWebServerFactoryCustomizerTests {
return requestHeaderSizes;
}
private BlockingQueue<?> getQueue(ThreadPool threadPool) {
return ReflectionTestUtils.invokeMethod(threadPool, "getQueue");
}
private void bind(String... inlinedProperties) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, inlinedProperties);
new Binder(ConfigurationPropertySources.get(this.environment)).bind("server",
......
......@@ -17,6 +17,7 @@
package org.springframework.boot.web.embedded.jetty;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.springframework.boot.web.server.ConfigurableWebServerFactory;
......@@ -36,6 +37,13 @@ public interface ConfigurableJettyWebServerFactory extends ConfigurableWebServer
*/
void setAcceptors(int acceptors);
/**
* Set the {@link ThreadPool} that should be used by the {@link Server}. If set to
* {@code null} (default), the {@link Server} creates a {@link ThreadPool} implicitly.
* @param threadPool the ThreadPool to be used
*/
void setThreadPool(ThreadPool threadPool);
/**
* Set the number of selector threads to use.
* @param selectors the number of selector threads to use
......
......@@ -139,11 +139,7 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
return this.threadPool;
}
/**
* Set a Jetty {@link ThreadPool} that should be used by the {@link Server}. If set to
* {@code null} (default), the {@link Server} creates a {@link ThreadPool} implicitly.
* @param threadPool a Jetty ThreadPool to be used
*/
@Override
public void setThreadPool(ThreadPool threadPool) {
this.threadPool = threadPool;
}
......
......@@ -484,11 +484,7 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor
return this.threadPool;
}
/**
* Set a Jetty {@link ThreadPool} that should be used by the {@link Server}. If set to
* {@code null} (default), the {@link Server} creates a {@link ThreadPool} implicitly.
* @param threadPool a Jetty ThreadPool to be used
*/
@Override
public void setThreadPool(ThreadPool threadPool) {
this.threadPool = threadPool;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment