Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Virtual thread pinning in AbstractOpenApiResource.getOpenApi #2281

Closed
mjagus opened this issue Jun 26, 2023 · 0 comments
Closed

Virtual thread pinning in AbstractOpenApiResource.getOpenApi #2281

mjagus opened this issue Jun 26, 2023 · 0 comments
Labels
enhancement New feature or request

Comments

@mjagus
Copy link

mjagus commented Jun 26, 2023

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-preview and -Djdk.tracePinnedThreads=full switches
  • 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.

@bnasslahsen bnasslahsen added the enhancement New feature or request label Jul 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants