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
Make @TransactionScoped thread safe (virtual and platform threads) #29157
Comments
Here is a single file reproducer, it happens in platform threads and virtual threads too. The key point is the expensive resoruce in the producer. Each request will create 4 instances of the transactionscoped bean instead of 1. If the Thread.sleep(100) is removed, the chance of error is smaller. package org.acme.reproducer;
import org.eclipse.microprofile.context.ThreadContext;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.transaction.TransactionScoped;
import javax.transaction.Transactional;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Path("/hello")
public class GreetingResource {
@Inject
ThreadContext threadContext;
@Inject
MyTransactionalBean myTransactionalBean;
private final ExecutorService someExecutor = Executors.newCachedThreadPool();
@GET
@Produces(MediaType.TEXT_PLAIN)
@Transactional
public String hello() {
System.err.println("Begin transaction");
// Call transaction bean 4 times
var f1 = someExecutor.submit(threadContext.contextualRunnable(() -> myTransactionalBean.doWork()));
var f2 = someExecutor.submit(threadContext.contextualRunnable(() -> myTransactionalBean.doWork()));
var f3 = someExecutor.submit(threadContext.contextualRunnable(() -> myTransactionalBean.doWork()));
var f4 = someExecutor.submit(threadContext.contextualRunnable(() -> myTransactionalBean.doWork()));
try {
f1.get();
f2.get();
f3.get();
f4.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
System.err.println("End transaction");
return "Hello from RESTEasy Reactive";
}
@Singleton
public static class MyBeanProducer {
@javax.enterprise.inject.Produces
@TransactionScoped
public MyTransactionalBean bean() {
System.err.println("Creating a bean instance! Only one should exist per transaction");
try {
// Simulate acquire expensive resource
Thread.sleep(100); // Cause race condition
} catch (Exception e) {
}
return new MyTransactionalBean();
}
}
public static class MyTransactionalBean {
public void doWork() {
try {
Thread.sleep(50);
} catch (Exception e) {
}
}
}
} Output:
|
I'm not quite sure that |
My use case is to start a JTA transaction, consume a message from an MQ and then create multiple virtual threads which utilize non-jdbc, but XA capable resources, e.g. a custom FoundationDB resource that is enlisted to the JTA transaction, then commit the whole transaction in an atomic unit. This custom FoundationDB resource supports concurrent access from multiple threads, and is enlisted as an LRCO item in the 2P commit. I would agree that for regular JDBC this is a non-issue since you use one thread, but it would be nice if users could use their cutom XA resources from multiple virtual threads, but of course this can also be implemented in a custom CDI scope which does the locking. |
Hm, it seems that the MP CP spec supports both variants, i.e. Propagation of Active Transactions for Serial Use, but not Parallel and Propagation of Active Transactions for Parallel Use. I'm not sure which variant is supported by Naryana/Quarkus. |
Note that you would need to make the |
I should note that the spec only guarantees the non-parallel version to work. The impl in Quarkus uses Narayana but some of the code here is copied over and/or slightly altered. |
In the JTA specification the transaction can only be shared between two threads if is suspended on one thread and resumed on the other. Some implementatations of MicroProfile Context Propagation do support sharing the context [1] and, behind the scenes, the narayana/Smallrye implementation [2] does the suspend and resume management. But to use this feature you would need use the MP Context Propagation APIs. The narayana-jta quarkus extension is pulling in that dependency [3]. [1] https://download.eclipse.org/microprofile/microprofile-context-propagation-1.0/microprofile-context-propagation.html#_propagation_of_active_transactions_for_serial_use_but_not_parallel |
So the MP CP providers are present in the JTA quarkus extension but I don't know where the tests would be - is their a test suite that checks quarkus for MP compliance (since the compliance TCK has plenty tests in this area)? |
@mmusgrov MP TCKs are all here - https://github.com/quarkusio/quarkus/tree/main/tcks/microprofile-context-propagation |
The optionality, if that's a real word, is in the implementation of the spec - the smallrye-jta one implements the option. |
@manovotn it's been a while since I looked (the first smallrye implementation of the spec definitely tested parallel use of JTA transactions using the narayana based implementation) so I am checking now to verify that my statement is still accurate ... |
The test for the feature is marked I asked for clarification over in the https://gitter.im/eclipse/microprofile-concurrency room. I couldn't find a way to link to the chat so, at risk of cross posting, the question I asked was:
|
The answer I got is that an implementation should raise IllegalStateException if the feature is unsupported and therefore the TCK cannot test compliance. So we'd need to add our own tests if we want to support this behaviour. |
Describe the bug
When a
@TransactionScoped
bean is accessed inside a transaction for a first time from 2 virtual threads started by the thread that started the transaction (e.g. StructuredTaskScope forks), a race condition can lead to two instances of the bean being created. The transaction context is properly propagated to the virtual threads by smallrye-context-propagation callable wrapping.Expected behavior
A reentrantlock is expected to be locked before line https://github.com/quarkusio/quarkus/blob/main/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/context/TransactionContext.java#L110 and released after getting the bean.
Actual behavior
No locking is done in
TransactionalContext
, thus a race condition can create two beansHow to Reproduce?
TransactionScoped
bean@Transactional
annotationTransactionScoped
bean@Transactional
methodRepeat a lot of times and watch for multiple bean creation per method call.
Output of
uname -a
orver
all platforms
Output of
java -version
19
GraalVM version (if different from Java)
No response
Quarkus version or git rev
2.13.3.Final
Build tool (ie. output of
mvnw --version
orgradlew --version
)all build tools
Additional information
No response
The text was updated successfully, but these errors were encountered: