Uploaded image for project: 'JBoss Transaction Manager'
  1. JBoss Transaction Manager
  2. JBTM-3789

Pinned carrier thread when using virtual threads

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Duplicate
    • Icon: Major Major
    • None
    • None
    • None
    • None

      Virtual thread is a feature provided since Java 19 (still in preview mode in 20), but that will be out of preview in 21.

      Unfortunately, you cannot use Narayana transaction manager when using virtual threads, as it's pinning the carrier thread.

      If we consider the following code (Quarkus application):

      ```java
      @POST
      @RunOnVirtualThread
      public Response create(@Valid Todo item) throws Exception{
      tx.begin();
      try

      { item.persist(); tx.commit(); return Response.status(Status.CREATED).entity(item).build(); }

      catch (Exception e)

      { tx.rollback(); return Response.serverError().build(); }

      }
      ```

      It pins the carrier thread several times. For example:

      ```
      Thread145,ForkJoinPool-1-worker-1,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/sun.nio.ch.Poller.poll2(Poller.java:139)
      java.base/sun.nio.ch.Poller.poll(Poller.java:102)
      java.base/sun.nio.ch.Poller.poll(Poller.java:87)
      java.base/sun.nio.ch.NioSocketImpl.park(NioSocketImpl.java:175)
      java.base/sun.nio.ch.NioSocketImpl.park(NioSocketImpl.java:196)
      java.base/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:304)
      java.base/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:340)
      java.base/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:789)
      java.base/java.net.Socket$SocketInputStream.read(Socket.java:1025)
      org.postgresql.core.VisibleBufferedInputStream.readMore(VisibleBufferedInputStream.java:161)
      org.postgresql.core.VisibleBufferedInputStream.ensureBytes(VisibleBufferedInputStream.java:128)
      org.postgresql.core.VisibleBufferedInputStream.ensureBytes(VisibleBufferedInputStream.java:113)
      org.postgresql.core.VisibleBufferedInputStream.read(VisibleBufferedInputStream.java:73)
      org.postgresql.core.PGStream.receiveChar(PGStream.java:465)
      org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2155)
      org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:368)
      org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:327)
      org.postgresql.jdbc.PgConnection.executeTransactionCommand(PgConnection.java:965)
      org.postgresql.jdbc.PgConnection.commit(PgConnection.java:987)
      io.agroal.pool.ConnectionHandler.transactionCommit(ConnectionHandler.java:357)
      io.agroal.narayana.LocalXAResource.commit(LocalXAResource.java:69)
      com.arjuna.ats.internal.jta.resources.arjunacore.XAResourceRecord.topLevelOnePhaseCommit(XAResourceRecord.java:702)
      com.arjuna.ats.arjuna.coordinator.BasicAction.onePhaseCommit(BasicAction.java:2400)
      com.arjuna.ats.arjuna.coordinator.BasicAction.End(BasicAction.java:1502) <== monitors:1
      com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator.end(TwoPhaseCoordinator.java:96)
      com.arjuna.ats.arjuna.AtomicAction.commit(AtomicAction.java:162)
      com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionImple.commitAndDisassociate(TransactionImple.java:1295)
      com.arjuna.ats.internal.jta.transaction.arjunacore.BaseTransaction.commit(BaseTransaction.java:128)
      io.quarkus.narayana.jta.runtime.NotifyingTransactionManager.commit(NotifyingTransactionManager.java:70)
      io.quarkus.sample.TodoResource.create(TodoResource.java:60)
      io.quarkus.sample.TodoResource_Subclass.create$$superforward(Unknown Source)
      io.quarkus.sample.TodoResource_Subclass$$function$$1.apply(Unknown Source)
      io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:77)
      io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:66)
      io.quarkus.hibernate.validator.runtime.interceptor.AbstractMethodValidationInterceptor.validateMethodInvocation(AbstractMethodValidationInterceptor.java:71)
      io.quarkus.hibernate.validator.runtime.jaxrs.ResteasyReactiveEndPointValidationInterceptor.validateMethodInvocation(ResteasyReactiveEndPointValidationInterceptor.java:21)
      io.quarkus.hibernate.validator.runtime.jaxrs.ResteasyReactiveEndPointValidationInterceptor_Bean.intercept(Unknown Source)
      io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:42)
      io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:34)
      io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:27)
      io.quarkus.sample.TodoResource_Subclass.create(Unknown Source)
      io.quarkus.sample.TodoResource$quarkusrestinvoker$create_ec74cb005a9edfee316c71df167406e1de658ba8.invoke(Unknown Source)
      org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)
      io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:141)
      org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:145)
      java.base/java.util.concurrent.ThreadPerTaskExecutor$TaskRunner.run(ThreadPerTaskExecutor.java:314)
      java.base/java.lang.VirtualThread.run(VirtualThread.java:305)
      java.base/java.lang.VirtualThread$VThreadContinuation.lambda$new$0(VirtualThread.java:177)
      java.base/jdk.internal.vm.Continuation.enter0(Continuation.java:327)
      java.base/jdk.internal.vm.Continuation.enter(Continuation.java:320)

      ```

      Pinning means that instead of unparking the virtual thread as it should, it blocks the carrier thread and thus may block the application as the other virtual threads cannot run on that carrier anymore.

      To reproduce the issue:

      The tests are configured to dump the stack trace when pinning the carrier thread.

      I believe the code base is using constructs not working nicely with virtual threads (synchronized block). Many projects have switched/are switching to ReentrantReadWriteLock instead. For example, the PG JDBC driver did that.

            Unassigned Unassigned
            cescoffi@redhat.com Clement Escoffier
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated:
              Resolved: