-
Bug
-
Resolution: Duplicate
-
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
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:
- Check the `narayana-pinning` branch from https://github.com/cescoffier/quarkus-todo-app (https://github.com/cescoffier/quarkus-todo-app/tree/narayana-pinning)
- Navigate into the `quarkus-todo-loom` directory
- Make sure you use Java 19 or 20
- Execute the test with `mvn clean test`
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.