Skip to content

Spring MVC with Virtual Threads and Lazy Init pins virtual threads at launch #33958

Closed as not planned
@chrisgleissner

Description

@chrisgleissner

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:

  • 3.4.0 - JVM logs pinned Virtual Threads
  • 3.3.6 - JVM does not log pinned Virtual Thraeds

Details

  1. 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
  1. 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) {

Metadata

Metadata

Assignees

No one assigned

    Labels

    in: coreIssues in core modules (aop, beans, core, context, expression)status: declinedA suggestion or change that we don't feel we should currently apply

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions