-
-
Notifications
You must be signed in to change notification settings - Fork 553
Description
Describe the bug
I've been experimenting recently with project Loom by replacing various thread pools with virtual thread executors and running my application with -Djdk.tracePinnedThreads=full JVM option to see if there are any problems with this setup. The stack trace below popped up when I replaced Tomcat's default thread pool with virtual thread executor and went to the /swagger-ui.html page via internet browser:
Thread[#64,ForkJoinPool-1-worker-2,5,CarrierThreads]
java.base/java.lang.VirtualThread$VThreadContinuation.onPinned(VirtualThread.java:183)
java.base/jdk.internal.vm.Continuation.onPinned0(Continuation.java:398)
java.base/jdk.internal.vm.Continuation.yield0(Continuation.java:390)
java.base/jdk.internal.vm.Continuation.yield(Continuation.java:357)
java.base/java.lang.VirtualThread.yieldContinuation(VirtualThread.java:428)
java.base/java.lang.VirtualThread.park(VirtualThread.java:566)
java.base/java.lang.System$2.parkVirtualThread(System.java:2630)
java.base/jdk.internal.misc.VirtualThreads.park(VirtualThreads.java:54)
java.base/java.util.concurrent.locks.LockSupport.park(LockSupport.java:369)
java.base/java.util.concurrent.ForkJoinTask.awaitDone(ForkJoinTask.java:461)
java.base/java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:668)
java.base/java.util.stream.ReduceOps$ReduceOp.evaluateParallel(ReduceOps.java:927)
java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
org.springdoc.core.service.OpenAPIService.initializeHiddenRestController(OpenAPIService.java:289)
org.springdoc.core.service.OpenAPIService.build(OpenAPIService.java:271)
org.springdoc.api.AbstractOpenApiResource.getOpenApi(AbstractOpenApiResource.java:319) <== monitors:1
org.springdoc.webmvc.api.OpenApiResource.openapiJson(OpenApiResource.java:124)
org.springdoc.webmvc.api.OpenApiWebMvcResource.openapiJson(OpenApiWebMvcResource.java:111)
java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
java.base/java.lang.reflect.Method.invoke(Method.java:578)
org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207)
org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974)
The virtual thread becomes pinned because AbstractOpenApiResource.getOpenApi method is synchronized and it indirectly calls OpenAPIService.initializeHiddenRestController which blocks the thread by waiting for parallel stream.
Please consider replacing synchronized modifier with ReentrantLock to circumvent thread pinning.
To Reproduce
- JDK 20 with
--enable-previewand-Djdk.tracePinnedThreads=fullswitches - spring-boot version: 3.0.6
- springdoc-openapi version: 2.1.0
Replace Tomcat's thread pool with virtual thread executor. In my case I defined following Spring Bean to achieve this:
@Bean
TomcatProtocolHandlerCustomizer<?> tomcatProtocolHandlerCustomizer() {
return handler -> {
ThreadFactory factory = Thread.ofVirtual().name("http").factory();
handler.setExecutor(Executors.newThreadPerTaskExecutor(factory));
};
}Expected behavior
Thread pinning should not happen i.e. rendering swagger-ui.html page should not produce any stacktrace in standard output when -Djdk.tracePinnedThreads=full switch is provided to the JVM.
Additional context
Thread pinning only happens the first time swagger-ui.html page is rendered or every time if springdoc.cache.disabled property is set to true.