diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java index ff00c56518..5973cd8383 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java @@ -1032,17 +1032,18 @@ public class ServerProperties { private Integer selectors = -1; /** - * Maximum number of threads. + * Minimum number of threads. */ - private Integer maxThreads = 200; + private int minThreads = 8; /** - * Minimum number of threads. + * Maximum number of threads. */ - private Integer minThreads = 8; + private int maxThreads = 200; /** - * Maximum capacity of the thread pools backing queue. + * Maximum capacity of the thread pool's backing queue. A default is computed + * based on the threading configuration. */ private Integer maxQueueCapacity; @@ -1095,19 +1096,19 @@ 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; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyConstrainedQueuedThreadPoolFactory.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyConstrainedQueuedThreadPoolFactory.java deleted file mode 100644 index 4ad0d10da6..0000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyConstrainedQueuedThreadPoolFactory.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2012-2019 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.web.embedded; - -import java.time.Duration; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.SynchronousQueue; - -import org.eclipse.jetty.util.BlockingArrayQueue; -import org.eclipse.jetty.util.thread.QueuedThreadPool; - -import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.autoconfigure.web.ServerProperties.Jetty; -import org.springframework.boot.web.embedded.jetty.JettyThreadPoolFactory; - -/** - * A {@link JettyThreadPoolFactory} that creates a thread pool that uses a backing queue - * with a max capacity if the {@link Jetty#maxQueueCapacity} is specified. If the max - * capacity is not specified then the factory will return null thus allowing the standard - * Jetty server thread pool to be used. - * - * @author Chris Bono - * @since 2.3.0 - */ -public class JettyConstrainedQueuedThreadPoolFactory implements JettyThreadPoolFactory { - - private ServerProperties serverProperties; - - public JettyConstrainedQueuedThreadPoolFactory(ServerProperties serverProperties) { - this.serverProperties = serverProperties; - } - - /** - *

- * Creates a {@link QueuedThreadPool} with the following settings (if - * {@link Jetty#maxQueueCapacity} is specified): - *

- * @return thread pool as described above or {@code null} if - * {@link Jetty#maxQueueCapacity} is not specified. - */ - @Override - public QueuedThreadPool create() { - - Integer maxQueueCapacity = this.serverProperties.getJetty().getMaxQueueCapacity(); - - // Max depth is not specified - let Jetty server use its defaults in this case - if (maxQueueCapacity == null) { - return null; - } - - BlockingQueue queue; - if (maxQueueCapacity == 0) { - /** - * This queue will cause jetty to reject requests whenever there is no idle - * thread available to handle them. If this queue is used, it is strongly - * recommended to set _minThreads equal to _maxThreads. Jetty's - * QueuedThreadPool class may not behave like a regular java thread pool and - * may not add threads properly when a SynchronousQueue is used. - */ - queue = new SynchronousQueue<>(); - } - else { - /** - * Create a queue of fixed size. This queue will not grow. If a request - * arrives and the queue is empty, the client will see an immediate - * "connection reset" error. - */ - queue = new BlockingArrayQueue<>(maxQueueCapacity); - } - - Integer maxThreadCount = this.serverProperties.getJetty().getMaxThreads(); - Integer minThreadCount = this.serverProperties.getJetty().getMinThreads(); - Duration threadIdleTimeout = this.serverProperties.getJetty().getThreadIdleTimeout(); - - return new QueuedThreadPool((maxThreadCount != null) ? maxThreadCount : 200, - (minThreadCount != null) ? minThreadCount : 8, - (threadIdleTimeout != null) ? (int) threadIdleTimeout.toMillis() : 60000, queue); - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java index c8d4ae81ec..54f3b42e08 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizer.java @@ -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 customizer) { - factory.addServerCustomizers((connector) -> { - ThreadPool threadPool = connector.getThreadPool(); - if (threadPool instanceof QueuedThreadPool) { - customizer.accept((QueuedThreadPool) threadPool); - } - }); + private ThreadPool determineThreadPool(ServerProperties.Jetty properties) { + BlockingQueue 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 determineBlockingQueue(Integer maxQueueCapacity) { + if (maxQueueCapacity == null) { + return null; + } + if (maxQueueCapacity == 0) { + return new SynchronousQueue<>(); + } + else { + return new BlockingArrayQueue<>(maxQueueCapacity); + } } private void customizeAccessLog(ConfigurableJettyWebServerFactory factory, diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryConfiguration.java index 0e2371212c..e03963cd3d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryConfiguration.java @@ -24,12 +24,8 @@ import reactor.netty.http.server.HttpServer; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.autoconfigure.web.embedded.JettyConstrainedQueuedThreadPoolFactory; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory; import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer; -import org.springframework.boot.web.embedded.jetty.JettyThreadPoolFactory; import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; import org.springframework.boot.web.embedded.netty.NettyRouteProvider; import org.springframework.boot.web.embedded.netty.NettyServerCustomizer; @@ -105,7 +101,6 @@ abstract class ReactiveWebServerFactoryConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(ReactiveWebServerFactory.class) @ConditionalOnClass({ org.eclipse.jetty.server.Server.class }) - @EnableConfigurationProperties(ServerProperties.class) static class EmbeddedJetty { @Bean @@ -114,17 +109,10 @@ abstract class ReactiveWebServerFactoryConfiguration { return new JettyResourceFactory(); } - @Bean - @ConditionalOnMissingBean - JettyThreadPoolFactory jettyThreadPoolFactory(ServerProperties serverProperties) { - return new JettyConstrainedQueuedThreadPoolFactory(serverProperties); - } - @Bean JettyReactiveWebServerFactory jettyReactiveWebServerFactory(JettyResourceFactory resourceFactory, - JettyThreadPoolFactory threadPoolFactory, ObjectProvider serverCustomizers) { + ObjectProvider serverCustomizers) { JettyReactiveWebServerFactory serverFactory = new JettyReactiveWebServerFactory(); - serverFactory.setThreadPool(threadPoolFactory.create()); serverFactory.getServerCustomizers().addAll(serverCustomizers.orderedStream().collect(Collectors.toList())); serverFactory.setResourceFactory(resourceFactory); return serverFactory; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration.java index fcf5fe44a4..03dbe4e92b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration.java @@ -32,12 +32,8 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.SearchStrategy; -import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.autoconfigure.web.embedded.JettyConstrainedQueuedThreadPoolFactory; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer; import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; -import org.springframework.boot.web.embedded.jetty.JettyThreadPoolFactory; import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer; @@ -94,20 +90,12 @@ class ServletWebServerFactoryConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class }) @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT) - @EnableConfigurationProperties(ServerProperties.class) static class EmbeddedJetty { @Bean - @ConditionalOnMissingBean - JettyThreadPoolFactory jettyThreadPoolFactory(ServerProperties serverProperties) { - return new JettyConstrainedQueuedThreadPoolFactory(serverProperties); - } - - @Bean - JettyServletWebServerFactory JettyServletWebServerFactory(JettyThreadPoolFactory threadPoolFactory, + JettyServletWebServerFactory JettyServletWebServerFactory( ObjectProvider serverCustomizers) { JettyServletWebServerFactory factory = new JettyServletWebServerFactory(); - factory.setThreadPool(threadPoolFactory.create()); factory.getServerCustomizers().addAll(serverCustomizers.orderedStream().collect(Collectors.toList())); return factory; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java index cf3560b066..8caebfdbf2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java @@ -75,7 +75,6 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Andrew McGhie * @author HaiTao Zhang * @author Rafiullah Hamedy - * @author Chris Bono */ class ServerPropertiesTests { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyConstrainedQueuedThreadPoolFactoryTest.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyConstrainedQueuedThreadPoolFactoryTest.java deleted file mode 100644 index 81b45c0147..0000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyConstrainedQueuedThreadPoolFactoryTest.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2012-2019 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.web.embedded; - -import java.lang.reflect.Method; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.SynchronousQueue; - -import org.eclipse.jetty.util.BlockingArrayQueue; -import org.eclipse.jetty.util.thread.QueuedThreadPool; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.context.properties.bind.Bindable; -import org.springframework.boot.context.properties.bind.Binder; -import org.springframework.boot.context.properties.source.ConfigurationPropertySources; -import org.springframework.mock.env.MockEnvironment; -import org.springframework.test.context.support.TestPropertySourceUtils; -import org.springframework.util.ReflectionUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link JettyConstrainedQueuedThreadPoolFactory}. - * - * @author Chris Bono - */ -class JettyConstrainedQueuedThreadPoolFactoryTest { - - private MockEnvironment environment; - - private ServerProperties serverProperties; - - private JettyConstrainedQueuedThreadPoolFactory factory; - - @BeforeEach - void setup() { - this.environment = new MockEnvironment(); - this.serverProperties = new ServerProperties(); - ConfigurationPropertySources.attach(this.environment); - this.factory = new JettyConstrainedQueuedThreadPoolFactory(this.serverProperties); - } - - @Test - void factoryReturnsNullWhenMaxCapacityNotSpecified() { - bind("server.jetty.max-queue-capacity="); - assertThat(this.factory.create()).isNull(); - } - - @Test - void factoryReturnsSynchronousQueueWhenMaxCapacityIsZero() { - bind("server.jetty.max-queue-capacity=0"); - QueuedThreadPool queuedThreadPool = this.factory.create(); - assertThat(getQueue(queuedThreadPool, SynchronousQueue.class)).isNotNull(); - } - - @Test - void factoryReturnsBlockingArrayQueueWithDefaultsWhenOnlyMaxCapacityIsSet() { - bind("server.jetty.max-queue-capacity=5150"); - QueuedThreadPool queuedThreadPool = this.factory.create(); - assertThat(queuedThreadPool.getMinThreads()).isEqualTo(8); - assertThat(queuedThreadPool.getMaxThreads()).isEqualTo(200); - assertThat(queuedThreadPool.getIdleTimeout()).isEqualTo(60000); - assertThat(getQueue(queuedThreadPool, BlockingArrayQueue.class).getMaxCapacity()).isEqualTo(5150); - } - - @Test - void factoryReturnsBlockingArrayQueueWithCustomValues() { - bind("server.jetty.max-queue-capacity=5150", "server.jetty.min-threads=200", "server.jetty.max-threads=1000", - "server.jetty.thread-idle-timeout=10000"); - QueuedThreadPool queuedThreadPool = this.factory.create(); - assertThat(queuedThreadPool.getMinThreads()).isEqualTo(200); - assertThat(queuedThreadPool.getMaxThreads()).isEqualTo(1000); - assertThat(queuedThreadPool.getIdleTimeout()).isEqualTo(10000); - assertThat(getQueue(queuedThreadPool, BlockingArrayQueue.class).getMaxCapacity()).isEqualTo(5150); - } - - private void bind(String... inlinedProperties) { - TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, inlinedProperties); - new Binder(ConfigurationPropertySources.get(this.environment)).bind("server", - Bindable.ofInstance(this.serverProperties)); - } - - static > T getQueue(QueuedThreadPool queuedThreadPool, - Class expectedQueueClass) { - Method getQueue = ReflectionUtils.findMethod(QueuedThreadPool.class, "getQueue"); - ReflectionUtils.makeAccessible(getQueue); - Object obj = ReflectionUtils.invokeMethod(getQueue, queuedThreadPool); - assertThat(obj).isInstanceOf(expectedQueueClass); - return expectedQueueClass.cast(obj); - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizerTests.java index 976928d0b7..4a1c989d0e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/JettyWebServerFactoryCustomizerTests.java @@ -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", diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryAutoConfigurationTests.java index 92f0f1491b..112c2efb4f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryAutoConfigurationTests.java @@ -22,18 +22,14 @@ import org.apache.catalina.Context; import org.apache.catalina.connector.Connector; import org.apache.catalina.startup.Tomcat; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.util.thread.QueuedThreadPool; -import org.eclipse.jetty.util.thread.ThreadPool; import org.junit.jupiter.api.Test; import reactor.netty.http.server.HttpServer; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.embedded.JettyConstrainedQueuedThreadPoolFactory; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory; import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer; -import org.springframework.boot.web.embedded.jetty.JettyThreadPoolFactory; import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; import org.springframework.boot.web.embedded.netty.NettyServerCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; @@ -57,7 +53,6 @@ import org.springframework.web.server.adapter.ForwardedHeaderTransformer; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.when; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -68,7 +63,6 @@ import static org.mockito.Mockito.verify; * @author Brian Clozel * @author Raheela Aslam * @author Madhura Bhave - * @author Chris Bono */ class ReactiveWebServerFactoryAutoConfigurationTests { @@ -248,36 +242,6 @@ class ReactiveWebServerFactoryAutoConfigurationTests { }); } - @Test - void jettyDefaultThreadPoolFactoryUsedToCreateThreadPoolOnWebServerFactory() { - new ReactiveWebApplicationContextRunner(AnnotationConfigReactiveWebServerApplicationContext::new) - .withConfiguration(AutoConfigurations.of(ReactiveWebServerFactoryAutoConfiguration.class)) - .withClassLoader(new FilteredClassLoader(Tomcat.class, HttpServer.class)) - .withUserConfiguration(DoubleRegistrationJettyServerCustomizerConfiguration.class, - HttpHandlerConfiguration.class) - .withPropertyValues("server.port=0", "server.jetty.max-queue-capacity:5150").run((context) -> { - assertThat(context.getBean(JettyThreadPoolFactory.class)) - .isInstanceOf(JettyConstrainedQueuedThreadPoolFactory.class); - JettyReactiveWebServerFactory factory = context.getBean(JettyReactiveWebServerFactory.class); - assertThat(factory.getThreadPool()).isNotNull(); - }); - } - - @Test - void jettyCustomThreadPoolFactoryUsedToCreateThreadPoolOnWebServerFactory() { - new ReactiveWebApplicationContextRunner(AnnotationConfigReactiveWebServerApplicationContext::new) - .withConfiguration(AutoConfigurations.of(ReactiveWebServerFactoryAutoConfiguration.class)) - .withClassLoader(new FilteredClassLoader(Tomcat.class, HttpServer.class)) - .withUserConfiguration(JettyCustomThreadPoolFactoryConfiguration.class, HttpHandlerConfiguration.class) - .withPropertyValues("server.port=0").run((context) -> { - JettyReactiveWebServerFactory factory = context.getBean(JettyReactiveWebServerFactory.class); - JettyThreadPoolFactory threadPoolFactory = context.getBean("jettyCustomThreadPoolFactory", - JettyThreadPoolFactory.class); - verify(threadPoolFactory, times(1)).create(); - assertThat(factory.getThreadPool()).isSameAs(JettyCustomThreadPoolFactoryConfiguration.threadPool); - }); - } - @Test void undertowBuilderCustomizerBeanIsAddedToFactory() { new ReactiveWebApplicationContextRunner(AnnotationConfigReactiveWebApplicationContext::new) @@ -504,20 +468,6 @@ class ReactiveWebServerFactoryAutoConfigurationTests { } - @Configuration(proxyBeanMethods = false) - static class JettyCustomThreadPoolFactoryConfiguration { - - static ThreadPool threadPool = new QueuedThreadPool(); - - @Bean - JettyThreadPoolFactory jettyCustomThreadPoolFactory() { - JettyThreadPoolFactory threadPoolFactory = mock(JettyThreadPoolFactory.class); - when(threadPoolFactory.create()).thenReturn(threadPool); - return threadPoolFactory; - } - - } - @Configuration(proxyBeanMethods = false) static class UndertowBuilderCustomizerConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfigurationTests.java index 6a292d0c16..cb7306bb0c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfigurationTests.java @@ -28,8 +28,6 @@ import org.apache.catalina.Context; import org.apache.catalina.connector.Connector; import org.apache.catalina.startup.Tomcat; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.util.thread.QueuedThreadPool; -import org.eclipse.jetty.util.thread.ThreadPool; import org.junit.jupiter.api.Test; import reactor.netty.http.server.HttpServer; @@ -37,14 +35,12 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.boot.autoconfigure.web.embedded.JettyConstrainedQueuedThreadPoolFactory; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer; import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; -import org.springframework.boot.web.embedded.jetty.JettyThreadPoolFactory; import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer; @@ -68,7 +64,6 @@ import org.springframework.web.servlet.FrameworkServlet; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.when; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -81,7 +76,6 @@ import static org.mockito.Mockito.verify; * @author Stephane Nicoll * @author Raheela Aslam * @author Madhura Bhave - * @author Chris Bono */ class ServletWebServerFactoryAutoConfigurationTests { @@ -187,40 +181,6 @@ class ServletWebServerFactoryAutoConfigurationTests { }); } - @Test - void jettyDefaultThreadPoolFactoryUsedToCreateThreadPoolOnWebServerFactory() { - WebApplicationContextRunner runner = new WebApplicationContextRunner( - AnnotationConfigServletWebServerApplicationContext::new) - .withClassLoader(new FilteredClassLoader(Tomcat.class, HttpServer.class)) - .withConfiguration(AutoConfigurations.of(ServletWebServerFactoryAutoConfiguration.class)) - .withPropertyValues("server.port:0", "server.jetty.max-queue-capacity:5150"); - runner.run((context) -> { - assertThat(context.getBean(JettyThreadPoolFactory.class)) - .isInstanceOf(JettyConstrainedQueuedThreadPoolFactory.class); - JettyServletWebServerFactory factory = context.getBean(JettyServletWebServerFactory.class); - assertThat(factory.getThreadPool()).isNotNull(); // its null by default so - // this verifies the - // factory was used - }); - } - - @Test - void jettyCustomThreadPoolFactoryUsedToCreateThreadPoolOnWebServerFactory() { - WebApplicationContextRunner runner = new WebApplicationContextRunner( - AnnotationConfigServletWebServerApplicationContext::new) - .withClassLoader(new FilteredClassLoader(Tomcat.class, HttpServer.class)) - .withConfiguration(AutoConfigurations.of(ServletWebServerFactoryAutoConfiguration.class)) - .withUserConfiguration(JettyCustomThreadPoolFactoryConfiguration.class) - .withPropertyValues("server.port:0"); - runner.run((context) -> { - JettyServletWebServerFactory factory = context.getBean(JettyServletWebServerFactory.class); - JettyThreadPoolFactory threadPoolFactory = context.getBean("jettyCustomThreadPoolFactory", - JettyThreadPoolFactory.class); - verify(threadPoolFactory, times(1)).create(); - assertThat(factory.getThreadPool()).isSameAs(JettyCustomThreadPoolFactoryConfiguration.threadPool); - }); - } - @Test void undertowDeploymentInfoCustomizerBeanIsAddedToFactory() { WebApplicationContextRunner runner = new WebApplicationContextRunner( @@ -619,20 +579,6 @@ class ServletWebServerFactoryAutoConfigurationTests { } - @Configuration(proxyBeanMethods = false) - static class JettyCustomThreadPoolFactoryConfiguration { - - static ThreadPool threadPool = new QueuedThreadPool(); - - @Bean - JettyThreadPoolFactory jettyCustomThreadPoolFactory() { - JettyThreadPoolFactory threadPoolFactory = mock(JettyThreadPoolFactory.class); - when(threadPoolFactory.create()).thenReturn(threadPool); - return threadPoolFactory; - } - - } - @Configuration(proxyBeanMethods = false) static class UndertowBuilderCustomizerConfiguration { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/ConfigurableJettyWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/ConfigurableJettyWebServerFactory.java index b80e4209bd..e6afc8319d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/ConfigurableJettyWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/ConfigurableJettyWebServerFactory.java @@ -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 diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactory.java index 950463a093..df883ab348 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactory.java @@ -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; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java index 85f57958a3..214370a673 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java @@ -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; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyThreadPoolFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyThreadPoolFactory.java deleted file mode 100644 index 0bcc936542..0000000000 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyThreadPoolFactory.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2012-2019 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.web.embedded.jetty; - -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.util.thread.ThreadPool; - -/** - * Factory to create a thread pool for use by a Jetty {@link Server}. - * - * @author Chris Bono - * @since 2.3.0 - * @param type of ThreadPool that the factory returns - */ -@FunctionalInterface -public interface JettyThreadPoolFactory { - - /** - * Creates a thread pool. - * @return a Jetty thread pool or null to use the default Jetty {@link Server} thread - * pool. - */ - T create(); - -}