I have a quarkus application with an async endpoint that creates an entity with default properties, starts a new thread within the request method and executes a long running job and then returns the entity as a response for the client to track.
#POST
#Transactional
public Response startJob(#NonNull JsonObject request) {
// create my entity
JobsRecord job = new JobsRecord();
// set default properties
job.setName(request.getString("name"));
// make persistent
jobsRepository.persist(job);
// start the long running job on a different thread
Executor.execute(() -> longRunning(job));
return Response.accepted().entity(job).build();
}
Additionally, the long running job will make updates to the entity as it runs and so it must also be transactional. However, the database entity just doesn't get updated.
These are the issues I am facing:
I get the following warnings:
ARJUNA012094: Commit of action id 0:ffffc0a80065:f2db:5ef4e1c7:0 invoked while multiple threads active within it.
ARJUNA012107: CheckedAction::check - atomic action 0:ffffc0a80065:f2db:5ef4e1c7:0 commiting with 2 threads active!
Seems like something that should be avoided.
I tried using #Transaction(value = TxType.REQUIRES_NEW) to no avail.
I tried using the API Approach instead of the #Transactional approach on longRunning as mentioned in the guide as follows:
#Inject UserTransaction transaction;
.
.
.
try {
transaction.begin();
jobsRecord.setStatus("Complete");
jobsRecord.setCompletedOn(new Timestamp(System.currentTimeMillis()));
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
transaction.rollback();
}
but then I get the errors: ARJUNA016051: thread is already associated with a transaction! and ARJUNA016079: Transaction rollback status is:ActionStatus.COMMITTED
I tried both the declarative and API based methods again this time with context propagation enabled. But still no luck.
Finally, based on the third approach, I thought keeping the #Transactional on the Http request handler and leaving longRunning as is without declarative or API based transaction approaches would work. However the database still does not get updated.
Clearly I am misunderstanding how JTA and context propagation works (among other things).
Is there a way (or even a design pattern) that allows me to update database entities asynchronously in a quarkus web application? Also why wouldn't any of the approaches I took have any effect?
Using quarkus 1.4.1.Final with ext: [agroal, cdi, flyway, hibernate-orm, hibernate-orm-panache, hibernate-validator, kubernetes-client, mutiny, narayana-jta, rest-client, resteasy, resteasy-jackson, resteasy-mutiny, smallrye-context-propagation, smallrye-health, smallrye-openapi, swagger-ui]
You should return an async type from your JAX-RS resource method, the transaction context will then be available when the async stage executes. There is some relevant documentation in the quarkus guide on context propagation.
I would start by looking at the one of the reactive examples such as the getting started quickstart. Try annotating each resource endpoint with #Transactional and the async code will run with a transaction context.
Related
I want my Rest Controller POST Endpoint to only allow one thread to execute the method and every other thread shall get 429 until the first thread is finished.
#ResponseStatus(code = HttpStatus.CREATED)
#PostMapping(value ="/myApp",consumes="application/json",produces="application/json")
public Execution execute(#RequestBody ParameterDTO StartDateParameter)
{
if(StartDateParameter.getStartDate()==null) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
}else {
if(Executer.isProcessAlive()) {
throw new ResponseStatusException(HttpStatus.TOO_MANY_REQUESTS);
}else {
return Executer.execute(StartDateParameter);
}
}
}
When I send multithreaded requests, every request gets 201. So I think the requests get in earlier than the isAlive() method beeing checked. How can I change it to only process the first request and "block" every other?
Lifecycle of a controller in spring is managed by the container and by default, it is singleton, which means that there is one instance of the bean created at startup and multiple threads can use it. The only way you can make it single threaded is if you use a synchronized block or handle the request call through an Executor service. But that defeats the entire purpose of using spring framework.
Spring provides other means to make your code thread safe. You can use the #Scope annotation to override the default scope. Since you are using a RestController, you could use the "request" scope (#Scope("request")), which creates a new instance to process your every http request. Doing it this way will make ensure that only 1 thread will be accessing your controller code at any given time.
I use Async with a method that call a remote service with Feign and I need to append an oauth2 token to the request, for that I use a RequestInterceptor.
#Bean
public RequestInterceptor requestTokenBearerInterceptor() {
return requestTemplate -> {
Object principal = SecurityContextHolder
.getContext()
.getAuthentication()
.getPrincipal();
if (!principal.equals("anonymousUser")) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)
SecurityContextHolder.getContext().getAuthentication().getDetails();
requestTemplate.header("Authorization", "bearer " + details.getTokenValue());
}
};
}
But when the requestInterceptor is used in another thread, I don't have acces to the same security context so getAuhentication return null.
I try to fix it in the executor configuration, I create a DelegatingSecurityContextExecutor wrapping the executor and the security context. But it seems that the bean is created in the 'main' thread and the security context is not the same used then, when a RestController method is executed, so the getAuthentication() still return null.
#Bean(name = "asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(3);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("AsynchThread-");
executor.initialize();
Executor wrappedExecutor = new DelegatingSecurityContextExecutor(executor, SecurityContextHolder.getContext());
return wrappedExecutor;
}
How can I configure the executor the right way ?
I finally found the solution, it is possible to propagate the security context automaticly to the other threads.
Just add this line of code in the static main method of your spring boot application :
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
The solution is well explained here : https://www.baeldung.com/spring-security-async-principal-propagation?fbclid=IwAR1zeGKvRvBb7GG8SmxO4x8-NlKkG39Q29WoLKxZ8NRzyKEcnDWx4Q6EUk0
!! WARNING !! : I noticed an unexpected behaviour with that solution, at least on my local dev environment. I'm connected to my app with two different accounts using sessionbox tool of chrome (same with different browsers), and it seems that when I 'm connected with user A SecurityContextHolder.getContext().getAuthentication().getPrincipal() return the security context of user B ... So Huge security problem ! I'm looking for a solution at the moment.
Reading this post : How to set up Spring Security SecurityContextHolder strategy? the solution seems to be here Spring Security and #Async (Authenticated Users mixed up)
It seems to me you can not use RequestInterceptor here. As far as I know when you use #Async you lose request context in the method which you want to execute in asynchronous way. To do so you have to explicitly pass access token to asynchronous method and provide it as request header:
#FeignClient(name = "userClient", url ="${userService.hostname}")
public interface MyFeignClient {
String AUTH_TOKEN = “Authorization”;
#GetMapping(“/users”)
List<User> findUsers(#RequestHeader(AUTH_TOKEN) String bearerToken);
}
For some reason Azure SQL does not seem to be picking with my MultipleActiveResultSets=True in the connection string of my dotnet core app.
my apps connection string looks like this
Server=tcp:xxx.database.windows.net,1433;Initial Catalog=xxx;User ID=xxx;Password=xxxxx;MultipleActiveResultSets=True;Encrypt=True;"
I still keep getting this error
A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
All my code has awaits when using async methods so I have no idea what else to do because this code works with local sql.
I am using DI with my DbContext and adding the service like this
services.AddDbContext<Models.Database.DBContext>();
Any help would be greatly appreciated.
UPDATE
The problem was in my Startup.cs. I missed it. OnTokenValidated had a method which was calling dbcontext but i never awaited the result. Didn't see any errors from the IDE cause normally it will warn you about not awaiting an async method. Updated the method with an await and added OnTokenValidated = async context. All fine now.
This
A second operation started on this context before a previous operation
completed. Any instance members are not guaranteed to be thread safe.
Is an EF error, not a SQL Server error. So MultipleActiveResultSets is irrelevant.
All my code has awaits when using async methods . . .I am using DI with my DbContext
So probably your DI is allowing a DbContext instance to be shared between requests.
I have a requirement of executing parent task which may or maynot have child task. Each parent and child task should be run in thread. If something goes wrong in parent or child execution the transaction of both parent and child task must be rollback. I am using hibernate4.
If I got it, the parent and the child task will run in differents threads.
According to me it's a very bad idea that does not worth considering.
While it may be possible using jta transaction, it's clearly not the case using hibernate transaction management delegation to underlying jdbc connection (you have one connection per session and MUST NOT share an hibernate session between threads).
Using jta you will have to handle connection retrieval and transactions yourself and can't so take advantages of connection pooling and container managed transaction (spring or java ee ones). It may be overcomplicated for about no performance improvments as sharing the database connection between two threads will just probably move the bottleneck one level below.
See how to share one transaction between multi threads
According to OP expectation here is a pseudo code for Hibernate 4 standalone session management with jdbc transaction (I personnaly advise to go with a container (Java ee or spring) and JTA container managed transaction)
In hibernate.cfg.xml
<property name="hibernate.current_session_context_class">thread</property>
SessionFactory :
Configuration configuration = new Configuration();
configuration.configure("hibernate.cfg.xml");
StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties());
SessionFactory sessionFactory = configuration.buildSessionFactory(builder.build());
The session factory should be exposed using a singleton (any way you choose you must have only one instance for the whole app)
public void executeParentTask() {
try {
sessionFactory.getCurrentSession().beginTransaction();
sessionFactory.getCurrentSession().persist(someEntity);
myChildTask.execute();
sessionFactory.getCurrentSession().getTransaction().commit();
}
catch (RuntimeException e) {
sessionFactory .getCurrentSession().getTransaction().rollback();
throw e; // or display error message
}
}
getCurrentSession() will return the session bound to the current thread. If you manage the thread execution yourself you should create the session at the beginning of the thread execution and close it at the end.
the child task will retrieve the same session than the parent one using sessionFactory.getCurrentSession()
See https://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html/ch03.html#configuration-sessionfactory
http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html_single/#transactions-demarcation-nonmanaged
You may find this interesting too : How to configure and get session in Hibernate 4.3.4.Final?
I have a problem regarding Hibernate and lazy loading.
Background:
I have a Spring MVC web app, I use Hibernate for my persistence layer. I'm using OpenSessionInViewFilter to enable me to lazy load entities in my view layer. And I'm extending the HibernateDaoSupport classes and using HibernateTemplate to save/load objects. Everything has been working quite well. Up until now.
The Problem:
I have a task which can be started via a web request. When the request is routed to a controller, the controller will create a new Runnable for this task and start the thread to run the task. So the original thread will return and the Hibernate session which was put in ThreadLocal (by OpenSessionInViewFilter) is not available to the new thread for the Task. So when the task does some database stuff I get the infamous LazyInitializationException.
Can any one suggest the best way I can make a Hibernate session available to the Task?
Thanks for reading.
Make your Runnable a Spring bean and add #Transactional annotation over run. You must be warned thou that this asynchronous task won't run in the same transaction as your web request.
And please don't start new thread, use pooling/executor.
Here is a working example on how to use the Hibernate session inside a Runnable:
#Service
#Transactional
public class ScheduleService {
#Autowired
private SessionFactory sessionFactory;
#Autowired
private ThreadPoolTaskScheduler scheduler;
public void doSomething() {
ScheduledFuture sf = scheduler.schedule(new Runnable() {
#Override
public void run() {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(scheduler);
final Session session = sessionFactory.openSession();
// Now you can use the session
}
}, new CronTrigger("25 8 * * * *"));
}
}
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext() takes a reference to any Spring managed bean, so the scheduler itself is fine. Any other Spring managed bean would work as well.
Do I understand correctly, you want to perform some action in a completely dedicated background thread, right? In that case, I recommend you not accessing the Hibernates OpenSessionInViewFilter and further session logic for that thread at all, because it will, is you correctly noted, run in a decoupled thread and therefore information loaded in the original thread (i.e, the one that dealt with the initial HttpRequest). I think it would be wise to open and close the session yourself within that thread.
Otherwise, you might question why you are running that operation in a separated thread. May be it is sufficient to run the operation normally and present the user with some 'loading' screen in the meantime?