Why is my Spring #Async bean method not being executed asychronously? - java

I have a Springboot application and I'm trying to execute an asynchronous method on a bean class inside a controller method. The problem is that my #Async method is not being executed asynchronously. Execution is halted until the method completes.
Can anyone tell me what I'm missing?
Here is my application class:
#SpringBootApplication
#EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
#Override
public void customize(Connector connector) {
connector.setPort(9000);
connector.setAsyncTimeout(60000);
}
});
return factory;
}
}
Here is my bean class:
public class LongProcess {
#Async
public Future<String> call() {
try {
System.out.println("Sleeping now...");
Thread.sleep(10000);
return new AsyncResult<String>("Hey");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
}
My configuration class:
#Configuration
#EnableAsync
public class LongProcessConfiguration implements AsyncConfigurer {
#Bean
public LongProcess longProcessBean() {
return new LongProcess();
}
#Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setMaxPoolSize(10);
taskExecutor.setThreadNamePrefix("LULExecutor-");
taskExecutor.initialize();
return taskExecutor;
}
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}
My controller method:
#RequestMapping("/utilities/longProcess")
public String longProcess() {
System.out.println("Starting long process...");
CsvFileDifferConfiguration context = new CsvFileDifferConfiguration();
LongProcess process = context.longProcessBean();
Future<String> result = process.call();
System.out.println("Done!");
return "{success: 1}";
}
This request unfortunately does not return immediately (I don't care about the result). The method is called successfully, but not in the background. Any idea what I might be missing?
As a test, if I change the controller method to wait for the result, the wait block is never entered:
#RequestMapping("/utilities/longProcess")
public String longProcess() throws InterruptedException {
System.out.println("Starting long process...");
CsvFileDifferConfiguration context = new CsvFileDifferConfiguration();
LongProcess process = context.longProcessBean();
Future<String> result = process.call();
while (!(result.isDone())) {
Thread.sleep(1); //10-millisecond pause between each check
System.out.println("Waiting for Long Process...");
}
System.out.println("Done!");
return "{success: 1}";
}

You have a mistake for the CDI usage.
If you manage your object using Spring Container you have to get deal just with ApplicationContext or its abilities like #Autowired.
The code
CsvFileDifferConfiguration context = new CsvFileDifferConfiguration();
is wrong.
Since you define your LongProcess as a #Bean you can just inject it to your #Controller:
#Autowired
privete LongProcess process;
and use it as before.
Using objects directly (e.g. new) loses the dependency injection features.
Read more Spring Docs, please.

Related

How to correctly stop the TimerTask

So basically I have a task with a delay that kills a VPN pod. I don't want to have a running pod when it's not needed.
The Desired behavior is when the service receives a request(REST) it cancels the existing task and creates a new one with further delay.
In my solution I use thread.stop() to cancel my task which has been deprecated for a while.
...
var VPN_TIMER_THREAD_NAME = "vpn-timer";
for (var thread : Thread.getAllStackTraces().keySet()) {
if (thread.getName().equals(VPN_TIMER_THREAD_NAME)) {
// Interrupted doesn't work for me
thread.stop();
}
}
var timer = new Timer(VPN_TIMER_THREAD_NAME);
timer.schedule(new TimerTask() {
#Override
public void run() {
// New Transaction for EM
TransactionStatus tx = VpnServiceImpl.this.txManager.getTransaction(new DefaultTransactionDefinition());
try {
var vpnToUpdate = VpnServiceImpl.this.em.find(Vpn.class, vpn.getId());
doTearDown(vpnToUpdate);
VpnServiceImpl.this.txManager.commit(tx);
} catch (RuntimeException e) {
log.error("Tear Down Error {}.", e.getMessage());
VpnServiceImpl.this.txManager.rollback(tx);
}
}
}, this.vpnProperties.delay());
...
private VpnStatusS2SDto doTearDown(Vpn vpn) {
log.debug("In the tear down");
this.client
.pods()
.inNamespace(this.kubeProps.getNamespace())
.withLabel("app", "vpn-gateway")
.withLabel("app.kubernetes.io/component", "vpn")
.delete();
entity.setModifiedDate(Instant.now());
this.em.persist(entity);
return mapper.toVpnStatusDto(entity);
}
When I'm changing to thread.interrupt() the doTearDown method is invoking more than once if I make more than one request.
With thread.stop it "kills" the previous task and creates a new one, indeed the tear down has been invoked only once.
I'm using Spring Boot.
Is there any way to implement that behavior?
Thanks in advance
According to the answer from ewramner I found the solution. It works as expected. Every new request cancels the existing task and creates a new one.
I've created the nested task class:
private class ShutDownTask extends TimerTask {
private final Vpn vpn;
private final PlatformTransactionManager txManager;
private final EntityManager em;
ShutDownTask(Vpn vpn, PlatformTransactionManager txManager, EntityManager em) {
this.vpn = vpn;
this.txManager = txManager;
this.em = em;
}
#Override
public void run() {
TransactionStatus tx = this.txManager.getTransaction(new
DefaultTransactionDefinition());
try {
var vpnToUpdate = this.em.find(Vpn.class, this.vpn.getId());
doTearDown(vpnToUpdate);
this.txManager.commit(tx);
} catch (RuntimeException e) {
this.txManager.rollback(tx);
}
}
}
In my service class:
#Service
public class VpnServiceImpl {
...
private final PlatformTransactionManager txManager;
private final EntityManager em;
private ShutDownTask shutDownTask;
...
if (this.shutDownTask != null) {
this.shutDownTask.cancel();
}
var timer = new Timer("vpn-timer");
this.shutDownTask = new ShutDownTask(vpn, this.txManager, this.em);
timer.schedule(this.shutDownTask, this.vpnProperties.delay());
...
}

Why my #Async Future<T> blocks?

I have 2 Synchronous methods, the following one does not block.
#RequestMapping("/send1")
#Async
public Future<Boolean> sendMail() throws InterruptedException {
System.out.println("sending mail 1..-"
+ Thread.currentThread().getName());
Thread.sleep(1000 * 16);
System.out.println("sending mail 1 completed");
return new AsyncResult<Boolean>(true);
}
But the following one blocks.
#RequestMapping("/send3")
public void callAsyn3() throws InterruptedException, ExecutionException {
Future<Boolean> go = sendMail3("test");
}
#Async
public Future<Boolean> sendMail3(String msg) throws InterruptedException {
boolean acceptedYet = false;
Thread.sleep(1000 * 12);
if (!msg.equalsIgnoreCase("")) {
acceptedYet = true;
}
return new AsyncResult<>(acceptedYet);
}
They are in the same controller class, Why such different behavior?
In the second case you call internally method. so the #Async is ignored (not proxyed method called).
There are two ways to fix
The first one is to ntroduce a separate bean (e.g. MyService) and move the annotated with #Async method there.
The second way is Autowire the controller to itself
#Controller
public class MyController {
#Autowired
private MyController myController;
#RequestMapping("/send3")
public void callAsyn3() throws InterruptedException, ExecutionException {
Future<Boolean> go = myController.sendMail3("test");
}
#Async
public Future<Boolean> sendMail3(String msg) throws InterruptedException {
boolean acceptedYet = false;
Thread.sleep(1000 * 12);
if (!msg.equalsIgnoreCase("")) {
acceptedYet = true;
}
return new AsyncResult<>(acceptedYet);
}
Calling method in the same class do not pass through the proxy. So you can not use a method with #Async in the same class and make calls asynchronous.
We can create another service and write the #Async method there. Something like this
#Service
public class MyService {
#Async
public Future<Boolean> sendMail3(String msg) throws InterruptedException {
boolean acceptedYet = false;
Thread.sleep(1000 * 12);
if (!msg.equalsIgnoreCase("")) {
acceptedYet = true;
}
return new AsyncResult<>(acceptedYet);
}
}
This will run asynchronously ( non-blocking ).
If you want to do this in the same controller, you can submit it manually to some thread pool.
you have self-invocation , call method sendMail3 from method callAsyn3 directly. it doesn't work because it bypasses the proxy and calls the underlying method directly.
simple fix - you should get contoller from context and call callAsyn3 from this instance.
normal fix - create new service - asyncSendMailComponent/Service, move sendMail3 into asyncSendMailComponent , inject asyncSendMailComponent into you controller and call sendMail3
in controller :
#Autowired
private AsyncSendMailComponent asyncSendMailComponent;
#RequestMapping("/send3")
public void callAsyn3() throws InterruptedException, ExecutionException {
Future<Boolean> go = asyncSendMailComponent.sendMail3(msg)
}
Async service
#Service
pubclic class AsyncSendMailComponent {
#Async
public Future<Boolean> sendMail3(String msg) throws InterruptedException {
boolean acceptedYet = false;
Thread.sleep(1000 * 12);
if (!msg.equalsIgnoreCase("")) {
acceptedYet = true;
}
return new AsyncResult<>(acceptedYet);
}
}

How to stub asynchronous calls with mockito?

Suppose I have a two classes that work together to execute a callable like this:
public class blah {
#Autowired
private ExecutorServiceUtil executorServiceUtil;
#Autowired
private RestTemplate restClient;
public SomeReturnType getDepositTransactions(HttpHeaders httpHeaders) {
ExecutorService executor = executorServiceUtil.createExecuter();
try {
DepositTransactionsAsyncResponse asyncResponse = getPersonalCollectionAsyncResponse( httpHeaders, executor);
// do some processing
// return appropriate return type
}finally {
executorServiceUtil.shutDownExecutor(executor);
}
}
Future<ResponseEntity<PersonalCollectionResponse>> getPersonalCollectionAsyncResponse( HttpHeaders httpHeaders, ExecutorService executor) {
PersonalCollectionRequest personalCollectionRequest = getpersonalCollectionRequest(); // getPersonalCollectionRequest populates the request appropriately
return executor.submit(() -> restClient.exchange(personalCollectionRequest, httpHeaders, PersonalCollectionResponse.class));
}
}
public class ExecutorServiceUtil {
private static Logger log = LoggerFactory.getLogger(ExecutorServiceUtil.class);
public ExecutorService createExecuter() {
return Executors.newCachedThreadPool();
}
public void shutDownExecutor(ExecutorService executor) {
try {
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
}
catch (InterruptedException e) {
log.error("Tasks were interrupted");
}
finally {
if (!executor.isTerminated()) {
log.error("Cancel non-finished tasks");
}
executor.shutdownNow();
}
}
}
How can I use Mockito to stub the a response and return it immediately?
I've tried the below but my innovcation.args() returns [null]
PowerMockito.when(executor.submit(Matchers.<Callable<ResponseEntity<OrxPendingPostedTrxCollectionResponseV3>>> any())).thenAnswer(new Answer<FutureTask<ResponseEntity<OrxPendingPostedTrxCollectionResponseV3>>>() {
#Override
public FutureTask<ResponseEntity<OrxPendingPostedTrxCollectionResponseV3>> answer(InvocationOnMock invocation) throws Throwable {
Object [] args = invocation.getArguments();
Callable<ResponseEntity<OrxPendingPostedTrxCollectionResponseV3>> callable = (Callable<ResponseEntity<OrxPendingPostedTrxCollectionResponseV3>>) args[0];
callable.call();
return null;
}
});
You do that by not using your ExecutorServiceUtil in your test code. What I mean is: you provide a mock of that util class to your production code!
And that mock does return a "same thread executor service"; instead of a "real service" (based on a thread pool). Writing such a same-thread-executor is actually straight forward - see here.
In other words: you want two different unit tests here:
You write unit tests for your ExecutorServiceUtil class in isolation; make sure it does the thing it is supposed to do (where I think: checking that it returns a non-null ExecutorService is almost good enough!)
You write unit tests for your blah class ... that use a mocked service. And all of a sudden, all your problems around "it is async" go away; because the "async" part vanishes in thin air.

Spring Boot: how to set Async timeout when deploying to an external server

While using the embedded tomcat for deploying my spring boot app, I set the async timeout as follows:
#Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
#Override
public void customize(Connector connector) {
connector.setAsyncTimeout(60000);
}
});
return factory;
}
But,how to achieve the same when deploying to an external server, for example, websphere?
Tried using the property:
spring.mvc.async.request-timeout=600000
But this did not have any effect.
Edit:
I had tried implementing AsyncConfigurer as per Andrei's suggestion. But it did not work as expected. Below is my configuration class:
#SpringBootApplication
#EnableAsync
public class Application implements AsyncConfigurer {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public Executor getAsyncExecutor() {
Executor executor = new ThreadPoolExecutor(10, 20, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10),
new ThreadPoolExecutor.AbortPolicy());
return executor;
}
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
// TODO Auto-generated method stub
return new SimpleAsyncUncaughtExceptionHandler();
}
}
I have given timeout as 60 seconds, but when trying this configuration, the request was timing out after 30 seconds. Was using RestClient.
Is there something I am missing?
In the SpringApplication (implement first the interface called AsyncConfigurer) class I would create my custom AsyncExeuctor like this:
#Override
public Executor getAsyncExecutor() {
Executor executor = new ThreadPoolExecutor(
poolSize,
maxSize,
keepAlive,
TimeUnit.SECONDS, // <--- TIMEOUT IN SECONDS
new ArrayBlockingQueue<>(qSize),
new ThreadPoolExecutor.AbortPolicy() // <-- It will abort if timeout exceeds
);
return executor;
}
You can configure the poolSize, maxSize, etc. in the application.properties file and then "inject" them using the #Value annotation.

Service not registered on startup

I am using JBoss5.1.x AS, EJB3.0. I am trying to add a job (using Quartz) to my deployment. I am registering a new Service, so it will init the scheduler on application deploy.
My problem is that the service never gets registered when I deploy my app.
My code:
Interface:
public interface ComponentMonitoringService
{
void create() throws Exception;
void start() throws Exception;
void stop();
void destroy();
}
Service:
#Service(objectName = "com.mirs.ecms.timer:service=ServerStartupManager")
#Management(ComponentMonitoringService.class)
public class ServerStartupManager implements ComponentMonitoringService
{
private SchedulerFactory schedulerFactory = null;
private Scheduler scheduler = null;
Logger logger = Logger.getLogger("ecms.log");
public void create() throws Exception
{
}
public void start() throws Exception
{
// Write your startup code
initScheduler();
}
private void initScheduler() throws ParseException, SchedulerException
{
schedulerFactory = new StdSchedulerFactory();
scheduler = schedulerFactory.getScheduler();
JobDetail startECMSJob = new JobDetail("startECMSJob", "group1", StartECMSJob.class);
CronTrigger trigger1 = new CronTrigger("cronTrigger", "TriggersGroup1", "0 0/5 * * * ?");
scheduler.scheduleJob(startECMSJob, trigger1);
scheduler.start();
}
public void stop()
{
try
{
scheduler.shutdown();
}
catch (Exception e)
{
logger.error("ServerStartupManager Failure occured during Manager stop", e);
}
}
public void destroy()
{
}
}
I found a solution.
I was not using the right annotation. I have to use EJB3 annotations.

Categories

Resources