Skip to content

Commit f8173eb

Browse files
committed
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
2 parents a6fdbdc + b56c4f1 commit f8173eb

File tree

7 files changed

+146
-34
lines changed

7 files changed

+146
-34
lines changed

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

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,15 +1031,21 @@ public static class Jetty {
10311031
*/
10321032
private Integer selectors = -1;
10331033

1034+
/**
1035+
* Minimum number of threads.
1036+
*/
1037+
private int minThreads = 8;
1038+
10341039
/**
10351040
* Maximum number of threads.
10361041
*/
1037-
private Integer maxThreads = 200;
1042+
private int maxThreads = 200;
10381043

10391044
/**
1040-
* Minimum number of threads.
1045+
* Maximum capacity of the thread pool's backing queue. A default is computed
1046+
* based on the threading configuration.
10411047
*/
1042-
private Integer minThreads = 8;
1048+
private Integer maxQueueCapacity;
10431049

10441050
/**
10451051
* Maximum thread idle time.
@@ -1090,22 +1096,30 @@ public void setSelectors(Integer selectors) {
10901096
this.selectors = selectors;
10911097
}
10921098

1093-
public void setMinThreads(Integer minThreads) {
1099+
public void setMinThreads(int minThreads) {
10941100
this.minThreads = minThreads;
10951101
}
10961102

1097-
public Integer getMinThreads() {
1103+
public int getMinThreads() {
10981104
return this.minThreads;
10991105
}
11001106

1101-
public void setMaxThreads(Integer maxThreads) {
1107+
public void setMaxThreads(int maxThreads) {
11021108
this.maxThreads = maxThreads;
11031109
}
11041110

1105-
public Integer getMaxThreads() {
1111+
public int getMaxThreads() {
11061112
return this.maxThreads;
11071113
}
11081114

1115+
public void setMaxQueueCapacity(Integer maxQueueCapacity) {
1116+
this.maxQueueCapacity = maxQueueCapacity;
1117+
}
1118+
1119+
public Integer getMaxQueueCapacity() {
1120+
return this.maxQueueCapacity;
1121+
}
1122+
11091123
public void setThreadIdleTimeout(Duration threadIdleTimeout) {
11101124
this.threadIdleTimeout = threadIdleTimeout;
11111125
}

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

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818

1919
import java.time.Duration;
2020
import java.util.Arrays;
21-
import java.util.function.Consumer;
21+
import java.util.concurrent.BlockingQueue;
22+
import java.util.concurrent.SynchronousQueue;
2223

2324
import org.eclipse.jetty.server.AbstractConnector;
2425
import org.eclipse.jetty.server.ConnectionFactory;
@@ -30,6 +31,7 @@
3031
import org.eclipse.jetty.server.handler.ContextHandler;
3132
import org.eclipse.jetty.server.handler.HandlerCollection;
3233
import org.eclipse.jetty.server.handler.HandlerWrapper;
34+
import org.eclipse.jetty.util.BlockingArrayQueue;
3335
import org.eclipse.jetty.util.thread.QueuedThreadPool;
3436
import org.eclipse.jetty.util.thread.ThreadPool;
3537

@@ -75,6 +77,7 @@ public void customize(ConfigurableJettyWebServerFactory factory) {
7577
ServerProperties properties = this.serverProperties;
7678
ServerProperties.Jetty jettyProperties = properties.getJetty();
7779
factory.setUseForwardHeaders(getOrDeduceUseForwardHeaders());
80+
factory.setThreadPool(determineThreadPool(jettyProperties));
7881
PropertyMapper propertyMapper = PropertyMapper.get();
7982
propertyMapper.from(jettyProperties::getAcceptors).whenNonNull().to(factory::setAcceptors);
8083
propertyMapper.from(jettyProperties::getSelectors).whenNonNull().to(factory::setSelectors);
@@ -83,12 +86,6 @@ public void customize(ConfigurableJettyWebServerFactory factory) {
8386
.addServerCustomizers(new MaxHttpHeaderSizeCustomizer(maxHttpHeaderSize)));
8487
propertyMapper.from(jettyProperties::getMaxHttpFormPostSize).asInt(DataSize::toBytes).when(this::isPositive)
8588
.to((maxHttpFormPostSize) -> customizeMaxHttpFormPostSize(factory, maxHttpFormPostSize));
86-
propertyMapper.from(jettyProperties::getMaxThreads).when(this::isPositive)
87-
.to((maxThreads) -> customizeThreadPool(factory, (threadPool) -> threadPool.setMaxThreads(maxThreads)));
88-
propertyMapper.from(jettyProperties::getMinThreads).when(this::isPositive)
89-
.to((minThreads) -> customizeThreadPool(factory, (threadPool) -> threadPool.setMinThreads(minThreads)));
90-
propertyMapper.from(jettyProperties::getThreadIdleTimeout).whenNonNull().asInt(Duration::toMillis).to(
91-
(idleTimeout) -> customizeThreadPool(factory, (threadPool) -> threadPool.setIdleTimeout(idleTimeout)));
9289
propertyMapper.from(properties::getConnectionTimeout).whenNonNull()
9390
.to((connectionTimeout) -> customizeIdleTimeout(factory, connectionTimeout));
9491
propertyMapper.from(jettyProperties::getConnectionIdleTimeout).whenNonNull()
@@ -144,13 +141,25 @@ else if (handler instanceof HandlerCollection) {
144141
});
145142
}
146143

147-
private void customizeThreadPool(ConfigurableJettyWebServerFactory factory, Consumer<QueuedThreadPool> customizer) {
148-
factory.addServerCustomizers((connector) -> {
149-
ThreadPool threadPool = connector.getThreadPool();
150-
if (threadPool instanceof QueuedThreadPool) {
151-
customizer.accept((QueuedThreadPool) threadPool);
152-
}
153-
});
144+
private ThreadPool determineThreadPool(ServerProperties.Jetty properties) {
145+
BlockingQueue<Runnable> queue = determineBlockingQueue(properties.getMaxQueueCapacity());
146+
int maxThreadCount = (properties.getMaxThreads() > 0) ? properties.getMaxThreads() : 200;
147+
int minThreadCount = (properties.getMinThreads() > 0) ? properties.getMinThreads() : 8;
148+
int threadIdleTimeout = (properties.getThreadIdleTimeout() != null)
149+
? (int) properties.getThreadIdleTimeout().toMillis() : 60000;
150+
return new QueuedThreadPool(maxThreadCount, minThreadCount, threadIdleTimeout, queue);
151+
}
152+
153+
private BlockingQueue<Runnable> determineBlockingQueue(Integer maxQueueCapacity) {
154+
if (maxQueueCapacity == null) {
155+
return null;
156+
}
157+
if (maxQueueCapacity == 0) {
158+
return new SynchronousQueue<>();
159+
}
160+
else {
161+
return new BlockingArrayQueue<>(maxQueueCapacity);
162+
}
154163
}
155164

156165
private void customizeAccessLog(ConfigurableJettyWebServerFactory factory,

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,12 @@ void testCustomizeJettyIdleTimeout() {
237237
assertThat(this.properties.getJetty().getThreadIdleTimeout()).hasSeconds(10);
238238
}
239239

240+
@Test
241+
void testCustomizeJettyMaxQueueCapacity() {
242+
bind("server.jetty.max-queue-capacity", "5150");
243+
assertThat(this.properties.getJetty().getMaxQueueCapacity()).isEqualTo(5150);
244+
}
245+
240246
@Test
241247
void testCustomizeUndertowServerOption() {
242248
bind("server.undertow.options.server.ALWAYS_SET_KEEP_ALIVE", "true");

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

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@
1818

1919
import java.io.File;
2020
import java.io.IOException;
21+
import java.time.Duration;
2122
import java.util.ArrayList;
2223
import java.util.Arrays;
2324
import java.util.List;
25+
import java.util.concurrent.BlockingQueue;
26+
import java.util.concurrent.SynchronousQueue;
2427
import java.util.stream.Collectors;
2528

2629
import org.eclipse.jetty.server.AbstractConnector;
@@ -30,11 +33,15 @@
3033
import org.eclipse.jetty.server.HttpConfiguration.ConnectionFactory;
3134
import org.eclipse.jetty.server.RequestLog;
3235
import org.eclipse.jetty.server.RequestLogWriter;
36+
import org.eclipse.jetty.server.Server;
37+
import org.eclipse.jetty.util.BlockingArrayQueue;
3338
import org.eclipse.jetty.util.thread.QueuedThreadPool;
39+
import org.eclipse.jetty.util.thread.ThreadPool;
3440
import org.junit.jupiter.api.BeforeEach;
3541
import org.junit.jupiter.api.Test;
3642

3743
import org.springframework.boot.autoconfigure.web.ServerProperties;
44+
import org.springframework.boot.autoconfigure.web.ServerProperties.Jetty;
3845
import org.springframework.boot.context.properties.bind.Bindable;
3946
import org.springframework.boot.context.properties.bind.Binder;
4047
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
@@ -43,6 +50,7 @@
4350
import org.springframework.boot.web.embedded.jetty.JettyWebServer;
4451
import org.springframework.mock.env.MockEnvironment;
4552
import org.springframework.test.context.support.TestPropertySourceUtils;
53+
import org.springframework.test.util.ReflectionTestUtils;
4654

4755
import static org.assertj.core.api.Assertions.assertThat;
4856
import static org.mockito.Mockito.mock;
@@ -135,29 +143,100 @@ void accessLogCanBeEnabled() {
135143
}
136144

137145
@Test
138-
void maxThreadsCanBeCustomized() {
146+
void threadPoolMatchesJettyDefaults() {
147+
ThreadPool defaultThreadPool = new Server(0).getThreadPool();
148+
ThreadPool configuredThreadPool = customizeAndGetServer().getServer().getThreadPool();
149+
assertThat(defaultThreadPool).isInstanceOf(QueuedThreadPool.class);
150+
assertThat(configuredThreadPool).isInstanceOf(QueuedThreadPool.class);
151+
QueuedThreadPool defaultQueuedThreadPool = (QueuedThreadPool) defaultThreadPool;
152+
QueuedThreadPool configuredQueuedThreadPool = (QueuedThreadPool) configuredThreadPool;
153+
assertThat(configuredQueuedThreadPool.getMinThreads()).isEqualTo(defaultQueuedThreadPool.getMinThreads());
154+
assertThat(configuredQueuedThreadPool.getMaxThreads()).isEqualTo(defaultQueuedThreadPool.getMaxThreads());
155+
assertThat(configuredQueuedThreadPool.getIdleTimeout()).isEqualTo(defaultQueuedThreadPool.getIdleTimeout());
156+
BlockingQueue<?> defaultQueue = getQueue(defaultThreadPool);
157+
BlockingQueue<?> configuredQueue = getQueue(configuredThreadPool);
158+
assertThat(defaultQueue).isInstanceOf(BlockingArrayQueue.class);
159+
assertThat(configuredQueue).isInstanceOf(BlockingArrayQueue.class);
160+
assertThat(((BlockingArrayQueue<?>) defaultQueue).getMaxCapacity())
161+
.isEqualTo(((BlockingArrayQueue<?>) configuredQueue).getMaxCapacity());
162+
}
163+
164+
@Test
165+
void threadPoolMaxThreadsCanBeCustomized() {
139166
bind("server.jetty.max-threads=100");
140167
JettyWebServer server = customizeAndGetServer();
141168
QueuedThreadPool threadPool = (QueuedThreadPool) server.getServer().getThreadPool();
142169
assertThat(threadPool.getMaxThreads()).isEqualTo(100);
143170
}
144171

145172
@Test
146-
void minThreadsCanBeCustomized() {
173+
void threadPoolMinThreadsCanBeCustomized() {
147174
bind("server.jetty.min-threads=100");
148175
JettyWebServer server = customizeAndGetServer();
149176
QueuedThreadPool threadPool = (QueuedThreadPool) server.getServer().getThreadPool();
150177
assertThat(threadPool.getMinThreads()).isEqualTo(100);
151178
}
152179

153180
@Test
154-
void threadIdleTimeoutCanBeCustomized() {
181+
void threadPoolIdleTimeoutCanBeCustomized() {
155182
bind("server.jetty.thread-idle-timeout=100s");
156183
JettyWebServer server = customizeAndGetServer();
157184
QueuedThreadPool threadPool = (QueuedThreadPool) server.getServer().getThreadPool();
158185
assertThat(threadPool.getIdleTimeout()).isEqualTo(100000);
159186
}
160187

188+
@Test
189+
void threadPoolWithMaxQueueCapacityEqualToZeroCreateSynchronousQueue() {
190+
bind("server.jetty.max-queue-capacity=0");
191+
JettyWebServer server = customizeAndGetServer();
192+
ThreadPool threadPool = server.getServer().getThreadPool();
193+
BlockingQueue<?> queue = getQueue(threadPool);
194+
assertThat(queue).isInstanceOf(SynchronousQueue.class);
195+
assertDefaultThreadPoolSettings(threadPool);
196+
}
197+
198+
@Test
199+
void threadPoolWithMaxQueueCapacityEqualToZeroCustomizesThreadPool() {
200+
bind("server.jetty.max-queue-capacity=0", "server.jetty.min-threads=100", "server.jetty.max-threads=100",
201+
"server.jetty.thread-idle-timeout=6s");
202+
JettyWebServer server = customizeAndGetServer();
203+
QueuedThreadPool threadPool = (QueuedThreadPool) server.getServer().getThreadPool();
204+
assertThat(threadPool.getMinThreads()).isEqualTo(100);
205+
assertThat(threadPool.getMaxThreads()).isEqualTo(100);
206+
assertThat(threadPool.getIdleTimeout()).isEqualTo(Duration.ofSeconds(6).toMillis());
207+
}
208+
209+
@Test
210+
void threadPoolWithMaxQueueCapacityPositiveCreateBlockingArrayQueue() {
211+
bind("server.jetty.max-queue-capacity=1234");
212+
JettyWebServer server = customizeAndGetServer();
213+
ThreadPool threadPool = server.getServer().getThreadPool();
214+
BlockingQueue<?> queue = getQueue(threadPool);
215+
assertThat(queue).isInstanceOf(BlockingArrayQueue.class);
216+
assertThat(((BlockingArrayQueue<?>) queue).getMaxCapacity()).isEqualTo(1234);
217+
assertDefaultThreadPoolSettings(threadPool);
218+
}
219+
220+
@Test
221+
void threadPoolWithMaxQueueCapacityPositiveCustomizesThreadPool() {
222+
bind("server.jetty.max-queue-capacity=1234", "server.jetty.min-threads=10", "server.jetty.max-threads=150",
223+
"server.jetty.thread-idle-timeout=3s");
224+
JettyWebServer server = customizeAndGetServer();
225+
QueuedThreadPool threadPool = (QueuedThreadPool) server.getServer().getThreadPool();
226+
assertThat(threadPool.getMinThreads()).isEqualTo(10);
227+
assertThat(threadPool.getMaxThreads()).isEqualTo(150);
228+
assertThat(threadPool.getIdleTimeout()).isEqualTo(Duration.ofSeconds(3).toMillis());
229+
}
230+
231+
private void assertDefaultThreadPoolSettings(ThreadPool threadPool) {
232+
assertThat(threadPool).isInstanceOf(QueuedThreadPool.class);
233+
QueuedThreadPool queuedThreadPool = (QueuedThreadPool) threadPool;
234+
Jetty defaultProperties = new Jetty();
235+
assertThat(queuedThreadPool.getMinThreads()).isEqualTo(defaultProperties.getMinThreads());
236+
assertThat(queuedThreadPool.getMaxThreads()).isEqualTo(defaultProperties.getMaxThreads());
237+
assertThat(queuedThreadPool.getIdleTimeout()).isEqualTo(defaultProperties.getThreadIdleTimeout().toMillis());
238+
}
239+
161240
private CustomRequestLog getRequestLog(JettyWebServer server) {
162241
RequestLog requestLog = server.getServer().getRequestLog();
163242
assertThat(requestLog).isInstanceOf(CustomRequestLog.class);
@@ -236,6 +315,10 @@ private List<Integer> getRequestHeaderSizes(JettyWebServer server) {
236315
return requestHeaderSizes;
237316
}
238317

318+
private BlockingQueue<?> getQueue(ThreadPool threadPool) {
319+
return ReflectionTestUtils.invokeMethod(threadPool, "getQueue");
320+
}
321+
239322
private void bind(String... inlinedProperties) {
240323
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, inlinedProperties);
241324
new Binder(ConfigurationPropertySources.get(this.environment)).bind("server",

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/ConfigurableJettyWebServerFactory.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.web.embedded.jetty;
1818

1919
import org.eclipse.jetty.server.Server;
20+
import org.eclipse.jetty.util.thread.ThreadPool;
2021

2122
import org.springframework.boot.web.server.ConfigurableWebServerFactory;
2223

@@ -36,6 +37,13 @@ public interface ConfigurableJettyWebServerFactory extends ConfigurableWebServer
3637
*/
3738
void setAcceptors(int acceptors);
3839

40+
/**
41+
* Set the {@link ThreadPool} that should be used by the {@link Server}. If set to
42+
* {@code null} (default), the {@link Server} creates a {@link ThreadPool} implicitly.
43+
* @param threadPool the ThreadPool to be used
44+
*/
45+
void setThreadPool(ThreadPool threadPool);
46+
3947
/**
4048
* Set the number of selector threads to use.
4149
* @param selectors the number of selector threads to use

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactory.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,7 @@ public ThreadPool getThreadPool() {
139139
return this.threadPool;
140140
}
141141

142-
/**
143-
* Set a Jetty {@link ThreadPool} that should be used by the {@link Server}. If set to
144-
* {@code null} (default), the {@link Server} creates a {@link ThreadPool} implicitly.
145-
* @param threadPool a Jetty ThreadPool to be used
146-
*/
142+
@Override
147143
public void setThreadPool(ThreadPool threadPool) {
148144
this.threadPool = threadPool;
149145
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -484,11 +484,7 @@ public ThreadPool getThreadPool() {
484484
return this.threadPool;
485485
}
486486

487-
/**
488-
* Set a Jetty {@link ThreadPool} that should be used by the {@link Server}. If set to
489-
* {@code null} (default), the {@link Server} creates a {@link ThreadPool} implicitly.
490-
* @param threadPool a Jetty ThreadPool to be used
491-
*/
487+
@Override
492488
public void setThreadPool(ThreadPool threadPool) {
493489
this.threadPool = threadPool;
494490
}

0 commit comments

Comments
 (0)