Closed as not planned
Closed as not planned
Description
Problem
When using Spring Boot 3.4.0 combined with Spring WebMVC, virtual threads, and lazy bean initialization, accessing /actuator/health
during the application launch results in a pinned virtual thread.
This is a regression from Spring Boot 3.3.6 where this issue does not occur.
Sample App
This issue is demonstrated in https://github.com/chrisgleissner/spring-boot-3.4-pinned-thread-during-launch-issue:
Details
- Start process that continuously calls /actuator/health:
until curl --output /dev/null --silent --head --fail http://localhost:8080/actuator/health; do printf '.'; sleep 0.01; done
- Start Spring Boot app
./gradlew bootRun
Software Versions
- Spring Boot: 3.4.0
- Java: OpenJDK 64-Bit Server VM Corretto-21.0.5.11.1 (build 21.0.5+11-LTS, mixed mode, sharing)
- OS: Ubuntu 24.04.1
- Gradle 8.11.1
Source code
build.gradle.kts
:
plugins {
java
id("org.springframework.boot") version "3.4.0"
id("io.spring.dependency-management") version "1.1.6"
}
group = "com.example"
version = "0.0.1-SNAPSHOT"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-web")
}
tasks.bootRun {
jvmArgs = listOf("-Djdk.tracePinnedThreads=full")
}
src/main/java/com/example/demo/DemoApplication.java
:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
src/main/resources/application.yaml
:
spring:
main:
lazy-initialization: true
threads:
virtual:
enabled: true
Expected Logs (Spring Boot 3.3.6)
No thread pinning detected.
./gradlew bootRun
> Task :bootRun
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.3.6)
2024-11-23T10:43:14.002Z INFO 193730 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication using Java 21.0.5 with PID 193730 (/home/chris/dev/loom-webflux-errors/3.4.0-monitor/sample/demo/build/classes/java/main started by chris in /home/chris/dev/loom-webflux-errors/3.4.0-monitor/sample/demo)
2024-11-23T10:43:14.005Z INFO 193730 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to 1 default profile: "default"
2024-11-23T10:43:14.653Z INFO 193730 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2024-11-23T10:43:14.663Z INFO 193730 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2024-11-23T10:43:14.663Z INFO 193730 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.33]
2024-11-23T10:43:14.692Z INFO 193730 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2024-11-23T10:43:14.693Z INFO 193730 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 659 ms
2024-11-23T10:43:14.871Z INFO 193730 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
2024-11-23T10:43:14.914Z INFO 193730 --- [omcat-handler-0] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2024-11-23T10:43:14.915Z INFO 193730 --- [omcat-handler-0] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2024-11-23T10:43:14.997Z INFO 193730 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 1 endpoint beneath base path '/actuator'
2024-11-23T10:43:15.008Z INFO 193730 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 1.293 seconds (process running for 1.493)
2024-11-23T10:43:15.062Z INFO 193730 --- [omcat-handler-0] o.s.web.servlet.DispatcherServlet : Completed initialization in 147 ms
Actual Logs (Spring Boot 3.4.0)
Thread pinning detected.
./gradlew bootRun
> Task :bootRun
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.4.0)
2024-11-23T10:44:40.524Z INFO 194398 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication using Java 21.0.5 with PID 194398 (/home/chris/dev/loom-webflux-errors/3.4.0-monitor/sample/demo/build/classes/java/main started by chris in /home/chris/dev/loom-webflux-errors/3.4.0-monitor/sample/demo)
2024-11-23T10:44:40.526Z INFO 194398 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to 1 default profile: "default"
2024-11-23T10:44:41.194Z INFO 194398 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2024-11-23T10:44:41.203Z INFO 194398 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2024-11-23T10:44:41.203Z INFO 194398 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.33]
2024-11-23T10:44:41.232Z INFO 194398 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2024-11-23T10:44:41.232Z INFO 194398 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 680 ms
2024-11-23T10:44:41.415Z INFO 194398 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
2024-11-23T10:44:41.450Z INFO 194398 --- [omcat-handler-0] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2024-11-23T10:44:41.450Z INFO 194398 --- [omcat-handler-0] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
VirtualThread[#37,tomcat-handler-0]/runnable@ForkJoinPool-1-worker-1 reason:MONITOR
java.base/java.lang.VirtualThread$VThreadContinuation.onPinned(VirtualThread.java:199)
java.base/jdk.internal.vm.Continuation.onPinned0(Continuation.java:393)
java.base/java.lang.VirtualThread.park(VirtualThread.java:596)
java.base/java.lang.System$2.parkVirtualThread(System.java:2643)
java.base/jdk.internal.misc.VirtualThreads.park(VirtualThreads.java:54)
java.base/java.util.concurrent.locks.LockSupport.park(LockSupport.java:219)
java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:754)
java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:990)
java.base/java.util.concurrent.locks.ReentrantLock$Sync.lock(ReentrantLock.java:153)
java.base/java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:322)
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:263)
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:334)
org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:204)
org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1278)
org.springframework.web.servlet.DispatcherServlet.initMultipartResolver(DispatcherServlet.java:527)
org.springframework.web.servlet.DispatcherServlet.initStrategies(DispatcherServlet.java:509)
org.springframework.web.servlet.DispatcherServlet.onRefresh(DispatcherServlet.java:501)
org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:603) <== monitors:1
org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:537)
org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.java:169)
jakarta.servlet.GenericServlet.init(GenericServlet.java:143)
jakarta.servlet.http.HttpServlet.init(HttpServlet.java:121)
org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:837) <== monitors:1
org.apache.catalina.core.StandardWrapper.allocate(StandardWrapper.java:588) <== monitors:1
org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:115)
org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483)
org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)
org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:397)
org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:905)
org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)
org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
java.base/java.lang.VirtualThread.run(VirtualThread.java:329)
2024-11-23T10:44:41.543Z INFO 194398 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 1 endpoint beneath base path '/actuator'
2024-11-23T10:44:41.557Z INFO 194398 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 1.314 seconds (process running for 1.538)
VirtualThread[#37,tomcat-handler-0]/runnable@ForkJoinPool-1-worker-1 reason:MONITOR
java.base/java.lang.VirtualThread$VThreadContinuation.onPinned(VirtualThread.java:199)
java.base/jdk.internal.vm.Continuation.onPinned0(Continuation.java:393)
java.base/java.lang.VirtualThread.park(VirtualThread.java:596)
java.base/java.lang.System$2.parkVirtualThread(System.java:2643)
java.base/jdk.internal.misc.VirtualThreads.park(VirtualThreads.java:54)
java.base/java.util.concurrent.locks.LockSupport.park(LockSupport.java:219)
java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:754)
java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:990)
java.base/java.util.concurrent.locks.ReentrantLock$Sync.lock(ReentrantLock.java:153)
java.base/java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:322)
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:263)
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:334)
org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:204)
org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1278)
org.springframework.web.servlet.DispatcherServlet.initThemeResolver(DispatcherServlet.java:577)
org.springframework.web.servlet.DispatcherServlet.initStrategies(DispatcherServlet.java:511)
org.springframework.web.servlet.DispatcherServlet.onRefresh(DispatcherServlet.java:501)
org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:603) <== monitors:1
org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:537)
org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.java:169)
jakarta.servlet.GenericServlet.init(GenericServlet.java:143)
jakarta.servlet.http.HttpServlet.init(HttpServlet.java:121)
org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:837) <== monitors:1
org.apache.catalina.core.StandardWrapper.allocate(StandardWrapper.java:588) <== monitors:1
org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:115)
org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483)
org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)
org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:397)
org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:905)
org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)
org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
java.base/java.lang.VirtualThread.run(VirtualThread.java:329)
2024-11-23T10:44:41.608Z INFO 194398 --- [omcat-handler-0] o.s.web.servlet.DispatcherServlet : Completed initialization in 158 ms
Monitors
This section shows the relevant code for each of the stack trace lines ending with <== monitors:1
in the stack trace above.
Monitor
Log:
org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:603) <== monitors:1
Code:
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
this.configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
wac = this.findWebApplicationContext();
}
if (wac == null) {
wac = this.createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
synchronized(this.onRefreshMonitor) {
this.onRefresh(wac);
}
}
Monitor
Log:
org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:837) <== monitors:1
Code:
private synchronized void initServlet(Servlet servlet) throws ServletException {
if (!this.instanceInitialized) {
try {
if (Globals.IS_SECURITY_ENABLED) {
boolean success = false;
Monitor
Log:
org.apache.catalina.core.StandardWrapper.allocate(StandardWrapper.java:588) <== monitors:1
Code:
public Servlet allocate() throws ServletException {
if (this.unloading) {
throw new ServletException(sm.getString("standardWrapper.unloading", new Object[]{this.getName()}));
} else {
boolean newInstance = false;
if (this.instance == null || !this.instanceInitialized) {
synchronized(this) {