Enable virtual threads on Jetty

Closes gh-35703
pull/35914/head
Moritz Halbritter 1 year ago
parent 23979e6ccf
commit 140c37ceba

@ -26,6 +26,7 @@ import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServe
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementWebServerFactoryCustomizer; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementWebServerFactoryCustomizer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.web.embedded.JettyVirtualThreadsWebServerFactoryCustomizer;
import org.springframework.boot.autoconfigure.web.embedded.JettyWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.JettyWebServerFactoryCustomizer;
import org.springframework.boot.autoconfigure.web.embedded.NettyWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.NettyWebServerFactoryCustomizer;
import org.springframework.boot.autoconfigure.web.embedded.TomcatVirtualThreadsWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.TomcatVirtualThreadsWebServerFactoryCustomizer;
@ -79,7 +80,8 @@ public class ReactiveManagementChildContextConfiguration {
super(beanFactory, ReactiveWebServerFactoryCustomizer.class, TomcatWebServerFactoryCustomizer.class, super(beanFactory, ReactiveWebServerFactoryCustomizer.class, TomcatWebServerFactoryCustomizer.class,
TomcatReactiveWebServerFactoryCustomizer.class, TomcatReactiveWebServerFactoryCustomizer.class,
TomcatVirtualThreadsWebServerFactoryCustomizer.class, JettyWebServerFactoryCustomizer.class, TomcatVirtualThreadsWebServerFactoryCustomizer.class, JettyWebServerFactoryCustomizer.class,
UndertowWebServerFactoryCustomizer.class, NettyWebServerFactoryCustomizer.class); JettyVirtualThreadsWebServerFactoryCustomizer.class, UndertowWebServerFactoryCustomizer.class,
NettyWebServerFactoryCustomizer.class);
} }
} }

@ -39,6 +39,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.embedded.JettyVirtualThreadsWebServerFactoryCustomizer;
import org.springframework.boot.autoconfigure.web.embedded.JettyWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.JettyWebServerFactoryCustomizer;
import org.springframework.boot.autoconfigure.web.embedded.TomcatVirtualThreadsWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.TomcatVirtualThreadsWebServerFactoryCustomizer;
import org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer;
@ -124,8 +125,8 @@ class ServletManagementChildContextConfiguration {
ServletManagementWebServerFactoryCustomizer(ListableBeanFactory beanFactory) { ServletManagementWebServerFactoryCustomizer(ListableBeanFactory beanFactory) {
super(beanFactory, ServletWebServerFactoryCustomizer.class, TomcatServletWebServerFactoryCustomizer.class, super(beanFactory, ServletWebServerFactoryCustomizer.class, TomcatServletWebServerFactoryCustomizer.class,
TomcatWebServerFactoryCustomizer.class, TomcatVirtualThreadsWebServerFactoryCustomizer.class, TomcatWebServerFactoryCustomizer.class, TomcatVirtualThreadsWebServerFactoryCustomizer.class,
JettyWebServerFactoryCustomizer.class, UndertowServletWebServerFactoryCustomizer.class, JettyWebServerFactoryCustomizer.class, JettyVirtualThreadsWebServerFactoryCustomizer.class,
UndertowWebServerFactoryCustomizer.class); UndertowServletWebServerFactoryCustomizer.class, UndertowWebServerFactoryCustomizer.class);
} }
@Override @Override

@ -84,6 +84,13 @@ public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
return new JettyWebServerFactoryCustomizer(environment, serverProperties); return new JettyWebServerFactoryCustomizer(environment, serverProperties);
} }
@Bean
@ConditionalOnVirtualThreads
JettyVirtualThreadsWebServerFactoryCustomizer jettyVirtualThreadsWebServerFactoryCustomizer(
ServerProperties serverProperties) {
return new JettyVirtualThreadsWebServerFactoryCustomizer(serverProperties);
}
} }
/** /**

@ -0,0 +1,60 @@
/*
* Copyright 2012-2023 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.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.springframework.boot.autoconfigure.web.ServerProperties;
/**
* Creates a {@link ThreadPool} for Jetty, applying the
* {@link ServerProperties.Jetty.Threads} properties.
*
* @author Moritz Halbritter
*/
final class JettyThreadPool {
private JettyThreadPool() {
}
static QueuedThreadPool create(ServerProperties.Jetty.Threads properties) {
BlockingQueue<Runnable> queue = determineBlockingQueue(properties.getMaxQueueCapacity());
int maxThreadCount = (properties.getMax() > 0) ? properties.getMax() : 200;
int minThreadCount = (properties.getMin() > 0) ? properties.getMin() : 8;
int threadIdleTimeout = (properties.getIdleTimeout() != null) ? (int) properties.getIdleTimeout().toMillis()
: 60000;
return new QueuedThreadPool(maxThreadCount, minThreadCount, threadIdleTimeout, queue);
}
private static BlockingQueue<Runnable> determineBlockingQueue(Integer maxQueueCapacity) {
if (maxQueueCapacity == null) {
return null;
}
if (maxQueueCapacity == 0) {
return new SynchronousQueue<>();
}
else {
return new BlockingArrayQueue<>(maxQueueCapacity);
}
}
}

@ -0,0 +1,56 @@
/*
* Copyright 2012-2023 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 org.eclipse.jetty.util.VirtualThreads;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.web.embedded.jetty.ConfigurableJettyWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.core.Ordered;
import org.springframework.util.Assert;
/**
* Activates virtual threads on the {@link ConfigurableJettyWebServerFactory}.
*
* @author Moritz Halbritter
* @since 3.2.0
*/
public class JettyVirtualThreadsWebServerFactoryCustomizer
implements WebServerFactoryCustomizer<ConfigurableJettyWebServerFactory>, Ordered {
private final ServerProperties serverProperties;
public JettyVirtualThreadsWebServerFactoryCustomizer(ServerProperties serverProperties) {
this.serverProperties = serverProperties;
}
@Override
public void customize(ConfigurableJettyWebServerFactory factory) {
Assert.state(VirtualThreads.areSupported(), "Virtual threads are not supported");
QueuedThreadPool threadPool = JettyThreadPool.create(this.serverProperties.getJetty().getThreads());
threadPool.setVirtualThreadsExecutor(VirtualThreads.getDefaultVirtualThreadsExecutor());
factory.setThreadPool(threadPool);
}
@Override
public int getOrder() {
return JettyWebServerFactoryCustomizer.ORDER + 1;
}
}

@ -18,8 +18,6 @@ package org.springframework.boot.autoconfigure.web.embedded;
import java.time.Duration; import java.time.Duration;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.ConnectionFactory;
@ -31,9 +29,6 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.HandlerWrapper; 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;
import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.cloud.CloudPlatform; import org.springframework.boot.cloud.CloudPlatform;
@ -60,6 +55,8 @@ import org.springframework.util.unit.DataSize;
public class JettyWebServerFactoryCustomizer public class JettyWebServerFactoryCustomizer
implements WebServerFactoryCustomizer<ConfigurableJettyWebServerFactory>, Ordered { implements WebServerFactoryCustomizer<ConfigurableJettyWebServerFactory>, Ordered {
static final int ORDER = 0;
private final Environment environment; private final Environment environment;
private final ServerProperties serverProperties; private final ServerProperties serverProperties;
@ -71,7 +68,7 @@ public class JettyWebServerFactoryCustomizer
@Override @Override
public int getOrder() { public int getOrder() {
return 0; return ORDER;
} }
@Override @Override
@ -79,7 +76,7 @@ public class JettyWebServerFactoryCustomizer
ServerProperties.Jetty properties = this.serverProperties.getJetty(); ServerProperties.Jetty properties = this.serverProperties.getJetty();
factory.setUseForwardHeaders(getOrDeduceUseForwardHeaders()); factory.setUseForwardHeaders(getOrDeduceUseForwardHeaders());
ServerProperties.Jetty.Threads threadProperties = properties.getThreads(); ServerProperties.Jetty.Threads threadProperties = properties.getThreads();
factory.setThreadPool(determineThreadPool(properties.getThreads())); factory.setThreadPool(JettyThreadPool.create(properties.getThreads()));
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(properties::getMaxConnections).to(factory::setMaxConnections); map.from(properties::getMaxConnections).to(factory::setMaxConnections);
map.from(threadProperties::getAcceptors).to(factory::setAcceptors); map.from(threadProperties::getAcceptors).to(factory::setAcceptors);
@ -151,27 +148,6 @@ public class JettyWebServerFactoryCustomizer
}); });
} }
private ThreadPool determineThreadPool(ServerProperties.Jetty.Threads properties) {
BlockingQueue<Runnable> queue = determineBlockingQueue(properties.getMaxQueueCapacity());
int maxThreadCount = (properties.getMax() > 0) ? properties.getMax() : 200;
int minThreadCount = (properties.getMin() > 0) ? properties.getMin() : 8;
int threadIdleTimeout = (properties.getIdleTimeout() != null) ? (int) properties.getIdleTimeout().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, private void customizeAccessLog(ConfigurableJettyWebServerFactory factory,
ServerProperties.Jetty.Accesslog properties) { ServerProperties.Jetty.Accesslog properties) {
factory.addServerCustomizers((server) -> { factory.addServerCustomizers((server) -> {

@ -0,0 +1,54 @@
/*
* Copyright 2012-2023 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 org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.web.embedded.jetty.ConfigurableJettyWebServerFactory;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.assertArg;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link JettyVirtualThreadsWebServerFactoryCustomizer}.
*
* @author Moritz Halbritter
*/
class JettyVirtualThreadsWebServerFactoryCustomizerTests {
@Test
@EnabledForJreRange(min = JRE.JAVA_21)
void shouldConfigureVirtualThreads() {
ServerProperties properties = new ServerProperties();
JettyVirtualThreadsWebServerFactoryCustomizer customizer = new JettyVirtualThreadsWebServerFactoryCustomizer(
properties);
ConfigurableJettyWebServerFactory factory = mock(ConfigurableJettyWebServerFactory.class);
customizer.customize(factory);
then(factory).should().setThreadPool(assertArg((threadPool) -> {
assertThat(threadPool).isInstanceOf(QueuedThreadPool.class);
QueuedThreadPool queuedThreadPool = (QueuedThreadPool) threadPool;
assertThat(queuedThreadPool.getVirtualThreadsExecutor()).isNotNull();
}));
}
}
Loading…
Cancel
Save