From f81787e65d9e3fd57c8b5da092645d59b95db279 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Wed, 14 Jun 2023 16:35:33 +0200 Subject: [PATCH] Enable virtual threads on Tomcat Closes gh-35704 --- ...veManagementChildContextConfiguration.java | 4 +- ...etManagementChildContextConfiguration.java | 6 +- ...verFactoryCustomizerAutoConfiguration.java | 7 ++ ...tualThreadsWebServerFactoryCustomizer.java | 47 ++++++++++++++ .../TomcatWebServerFactoryCustomizer.java | 4 +- ...hreadsWebServerFactoryCustomizerTests.java | 64 +++++++++++++++++++ 6 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatVirtualThreadsWebServerFactoryCustomizer.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatVirtualThreadsWebServerFactoryCustomizerTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfiguration.java index 657f9dbda20d..faa2522016ea 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfiguration.java @@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.web.embedded.JettyWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.NettyWebServerFactoryCustomizer; +import org.springframework.boot.autoconfigure.web.embedded.TomcatVirtualThreadsWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.UndertowWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryCustomizer; @@ -76,7 +77,8 @@ static class ReactiveManagementWebServerFactoryCustomizer ReactiveManagementWebServerFactoryCustomizer(ListableBeanFactory beanFactory) { super(beanFactory, ReactiveWebServerFactoryCustomizer.class, TomcatWebServerFactoryCustomizer.class, - TomcatReactiveWebServerFactoryCustomizer.class, JettyWebServerFactoryCustomizer.class, + TomcatReactiveWebServerFactoryCustomizer.class, + TomcatVirtualThreadsWebServerFactoryCustomizer.class, JettyWebServerFactoryCustomizer.class, UndertowWebServerFactoryCustomizer.class, NettyWebServerFactoryCustomizer.class); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ServletManagementChildContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ServletManagementChildContextConfiguration.java index 44de3225efc5..bc0b6c42c3b6 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ServletManagementChildContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ServletManagementChildContextConfiguration.java @@ -40,6 +40,7 @@ import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.embedded.JettyWebServerFactoryCustomizer; +import org.springframework.boot.autoconfigure.web.embedded.TomcatVirtualThreadsWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.embedded.UndertowWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryCustomizer; @@ -122,8 +123,9 @@ static class ServletManagementWebServerFactoryCustomizer ServletManagementWebServerFactoryCustomizer(ListableBeanFactory beanFactory) { super(beanFactory, ServletWebServerFactoryCustomizer.class, TomcatServletWebServerFactoryCustomizer.class, - TomcatWebServerFactoryCustomizer.class, JettyWebServerFactoryCustomizer.class, - UndertowServletWebServerFactoryCustomizer.class, UndertowWebServerFactoryCustomizer.class); + TomcatWebServerFactoryCustomizer.class, TomcatVirtualThreadsWebServerFactoryCustomizer.class, + JettyWebServerFactoryCustomizer.class, UndertowServletWebServerFactoryCustomizer.class, + UndertowWebServerFactoryCustomizer.class); } @Override diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/EmbeddedWebServerFactoryCustomizerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/EmbeddedWebServerFactoryCustomizerAutoConfiguration.java index eca465353db6..300594855e37 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/EmbeddedWebServerFactoryCustomizerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/EmbeddedWebServerFactoryCustomizerAutoConfiguration.java @@ -29,6 +29,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWarDeployment; +import org.springframework.boot.autoconfigure.condition.ConditionalOnVirtualThreads; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -62,6 +63,12 @@ public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environ return new TomcatWebServerFactoryCustomizer(environment, serverProperties); } + @Bean + @ConditionalOnVirtualThreads + TomcatVirtualThreadsWebServerFactoryCustomizer tomcatVirtualThreadsProtocolHandlerCustomizer() { + return new TomcatVirtualThreadsWebServerFactoryCustomizer(); + } + } /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatVirtualThreadsWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatVirtualThreadsWebServerFactoryCustomizer.java new file mode 100644 index 000000000000..9af929ffaa82 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatVirtualThreadsWebServerFactoryCustomizer.java @@ -0,0 +1,47 @@ +/* + * 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.apache.coyote.ProtocolHandler; +import org.apache.tomcat.util.threads.VirtualThreadExecutor; + +import org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.core.Ordered; + +/** + * Activates {@link VirtualThreadExecutor} on {@link ProtocolHandler Tomcat's protocol + * handler}. + * + * @author Moritz Halbritter + * @since 3.2.0 + */ +public class TomcatVirtualThreadsWebServerFactoryCustomizer + implements WebServerFactoryCustomizer, Ordered { + + @Override + public void customize(ConfigurableTomcatWebServerFactory factory) { + factory.addProtocolHandlerCustomizers( + (protocolHandler) -> protocolHandler.setExecutor(new VirtualThreadExecutor("tomcat-handler-"))); + } + + @Override + public int getOrder() { + return TomcatWebServerFactoryCustomizer.order + 1; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java index e47b7cccfe59..050edc743384 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.java @@ -67,6 +67,8 @@ public class TomcatWebServerFactoryCustomizer implements WebServerFactoryCustomizer, Ordered { + static final int order = 0; + private final Environment environment; private final ServerProperties serverProperties; @@ -78,7 +80,7 @@ public TomcatWebServerFactoryCustomizer(Environment environment, ServerPropertie @Override public int getOrder() { - return 0; + return order; } @Override diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatVirtualThreadsWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatVirtualThreadsWebServerFactoryCustomizerTests.java new file mode 100644 index 000000000000..5fcf72d1f937 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/TomcatVirtualThreadsWebServerFactoryCustomizerTests.java @@ -0,0 +1,64 @@ +/* + * 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.function.Consumer; + +import org.apache.tomcat.util.threads.VirtualThreadExecutor; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; + +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.embedded.tomcat.TomcatWebServer; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link TomcatVirtualThreadsWebServerFactoryCustomizer}. + * + * @author Moritz Halbritter + */ +class TomcatVirtualThreadsWebServerFactoryCustomizerTests { + + private final TomcatVirtualThreadsWebServerFactoryCustomizer customizer = new TomcatVirtualThreadsWebServerFactoryCustomizer(); + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void shouldSetVirtualThreadExecutor() { + withWebServer((webServer) -> assertThat(webServer.getTomcat().getConnector().getProtocolHandler().getExecutor()) + .isInstanceOf(VirtualThreadExecutor.class)); + } + + private TomcatWebServer getWebServer() { + TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(0); + this.customizer.customize(factory); + return (TomcatWebServer) factory.getWebServer(); + } + + private void withWebServer(Consumer callback) { + TomcatWebServer webServer = getWebServer(); + webServer.start(); + try { + callback.accept(webServer); + } + finally { + webServer.stop(); + } + } + +}