Run task few seconds/minutes/hours later after a call - java

Does Spring Boot have something that allows to run a task several minutes later after a call?
Update:
Found that Spring Boot has ThreadPoolTaskScheduler. That allows to implement one time task execution with a time delay. See the answer below

First, we need to define a Bean
#Bean
public ThreadPoolTaskScheduler getScheduler(){
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
return scheduler;
}
The task that needs to be executed
public class WakeUpCallTask implements Runnable {
private String parameter1;
private String parameter2;
public WakeUpCallTask(String parameter1, String parameter2) {
super();
this.parameter1= parameter1;
this.parameter2= parameter2;
}
#Override
public void run() {
// Code that should be executed
}
}
Endpoint example - scheduling a task in 10000ms
#Autowired
ThreadPoolTaskScheduler scheduler;
#PostMapping(value = "/wake-me-up", consumes = "application/json")
#ResponseStatus(HttpStatus.OK)
public void scheduleCall(#RequestBody WakeMeUpRequest request) {
scheduler.schedule(
new WakeUpCallTask(request.getParameter1(), request.getParameter2()),
new Date(System.currentTimeMillis() + 10000));
}

Related

Spring #Async propagate context information

I've a Spring Boot 2.2 application. I created a service like this:
#Async
#PreAuthorize("hasAnyRole('ROLE_PBX')")
#PlanAuthorization(allowedPlans = {PlanType.BUSINESS, PlanType.ENTERPRISE})
public Future<AuditCdr> saveCDR(Cdr3CXDto cdrRecord) {
log.debug("Current tenant {}", TenantContext.getCurrentTenantId());
return new AsyncResult<AuditCdr>(auditCdrRepository.save(cdr3CXMapper.cdr3CXDtoToAuditCdr(cdrRecord)));
}
this is my #Async configuration:
#Configuration
#EnableAsync
public class AsyncConfiguration implements AsyncConfigurer {
#Override
public Executor getAsyncExecutor() {
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("threadAsync");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.initialize();
return executor;
}
}
Using SecurityContextHolder.MODE_INHERITABLETHREADLOCAL I see the Security context is passed to the #Async method.
In my multi-tenant application I use a ThreadLocal to set the tenant's id:
public class TenantContext {
public final static String TENANT_DEFAULT = "empty";
private static final ThreadLocal<String> code = new ThreadLocal<>();
public static void setCurrentTenantId(String code) {
if (code != null)
TenantContext.code.set(code);
}
public static String getCurrentTenantId() {
String tenantId = code.get();
if (StringUtils.isNotBlank(tenantId)) {
return tenantId;
}
return TENANT_DEFAULT;
}
public static void clear() {
code.remove();
}
}
Because ThreadLocal is related to the thread, it's not available in the #Async method. Furthemore my custom #PlanAuthorization aop needs it to perform verifications of the tenant's plan.
Is there a clean way to set TenantContext in any #Async method in my application?
I ended up to use a TaskDecorator:
#Log4j2
public class MdcTaskDecorator implements TaskDecorator {
#Override
public Runnable decorate(Runnable runnable) {
// Right now: Web thread context !
// (Grab the current thread MDC data)
String tenantId = TenantContext.getCurrentTenantId();
Long storeId = StoreContext.getCurrentStoreId();
SecurityContext securityContext = SecurityContextHolder.getContext();
Map<String, String> contextMap = MDC.getCopyOfContextMap();
log.info("Saving tenant information for async thread...");
return () -> {
try {
// Right now: #Async thread context !
// (Restore the Web thread context's MDC data)
TenantContext.setCurrentTenantId(tenantId);
StoreContext.setCurrentStoreId(storeId);
SecurityContextHolder.setContext(securityContext);
MDC.setContextMap(contextMap);
log.info("Restoring tenant information for async thread...");
runnable.run();
} catch (Throwable e) {
log.error("Error in async task", e);
} finally {
MDC.clear();
}
};
}
}
and I used it in this way:
#Configuration
#EnableAsync
public class AsyncConfiguration implements AsyncConfigurer {
#Override
public Executor getAsyncExecutor() {
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(1);
executor.setMaxPoolSize(100);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("threadAsync");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setTaskDecorator(new MdcTaskDecorator());
executor.initialize();
return executor;
}
}
It works and it seems also a neat solution.
The solution for such case is to :
configure custom thread pool so that you override it's execute method to sets up your thread local (or
executes any task from your main context), decorate the task and submit decorated task for execution instead of original one
instruct #Async annotation to use concrete thread pool
#Bean("tenantExecutor")
public Executor threadLocalAwareThreadPool() {
final CustomizableThreadFactory threadNameAwareFactory =
new CustomizableThreadFactory("threadAsync");
final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 10,
0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(500), threadNameAwareFactory) {
// override original method of thread pool
#Override
public void execute(Runnable originalTask) {
final String tenantId = tenantThreadLocal.get(); // read data from current before passing the task to async thread
// decorate the actual task by creating new task (Runnable) where you first set up the thread local and then execute your actual task
super.execute(() -> {
tenantThreadLocal.set(tenantId); // set data in actual async thread
originalTask.run();
});
}
};
return threadPoolExecutor;
}
Now we tell spring use our custom executor
#Async("tenantExecutor")
public Future<AuditCdr> saveCDR(Cdr3CXDto cdrRecord) {
// your code....
}
Instead of ThreadLocal you must use InheritableThreadLocal. Then you will see the values from the parent thread.
API Doc: https://docs.oracle.com/javase/8/docs/api/java/lang/InheritableThreadLocal.html
Here is an article about this in combination with Spring: https://medium.com/#hariohmprasath/async-process-using-spring-and-injecting-user-context-6f1af16e9759

Spring boot immediate return result fails to execute function

I want to return immediate the result of a database query but I want to start a different thread that does some things.
Now I tried with an Executor like this:
Executors.newScheduledThreadPool(1).schedule(
() -> fooFunction(),
1, TimeUnit.SECONDS
);
but the function is not executed after the method returns.
Complete code:
#Override
#Transactional
public FooDto updateInfo(UpdateTaskDto updateTask) {
// updating the entity
Executors.newScheduledThreadPool(1).schedule(
() -> fooFunction(),
1, TimeUnit.SECONDS
);
return FooDto()
}
Where fooFunction is just a function that saves something to the database and returns void.
This updateInfo function is inside a #Service annotated class.
Why is this not working?
EDIT:
#Transactional
#Override
public update() {
if (hasStartDateChanges || hasEndDateChanges) {
taskExecutor.execute(() -> {
setNotifications(changedTaskDto, NotificationType.TASK_UPDATE_TIME, true, taskEntity.getProject().getCompany().getId(), currentUser);
});
}
}
public void setNotifications(TaskDto task, NotificationType type, boolean visibleForClient, Long companyId, UserDto currentUser) {
ProjectEntity projectEntity = repository.findBy(task.getProjectId());
}
You can simply inject the TaskExecutor or TaskScheduler and use it:
#RestController
#RequestMapping("/demo")
public static class DemoRestController {
private final TaskExecutor taskExecutor;
public DemoRestController(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
#GetMapping
public String get() {
taskExecutor.execute(() -> {
System.out.println("Hello from Task");
});
return "hello";
}
}
From the docs:
Task Execution and Scheduling In the absence of an Executor bean in the context, Spring Boot auto-configures a ThreadPoolTaskExecutor
with sensible defaults that can be automatically associated to
asynchronous task execution (#EnableAsync) and Spring MVC asynchronous
request processing.
Source: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-task-execution-scheduling

runtime rescheduling #scheduled cron expression without restarting app

I have used scheduled annotation to schedule the task reading cronexpression from db. But when we change cron expression using UI , it needs to restart the app. How to inject TaskScheduler and ScheduledFuture or any other approach to reschedule without restarting when user update from UI which will call this method updateTestJobSchedule . Any example as per below code will be much helpful.
Currently i have : -
#Configuration
public class BasicConfig {
#Autowired
private TestJobSchedulerRepository testJobSchedulerRepository;
#Bean
public String getCronExpressionFromDb(){
return testJobSchedulerRepository.findByIsActive(1).getCronExpression();
}
}
#RestController
#EnableScheduling
#RequestMapping("/api")
public class TestJobController {
#Scheduled(cron="#{getCronExpressionFromDb}")
public void doTestJob(){
// does the job
}
// This update cron expression when request comes from UI
#RequestMapping(value = "/update-testjob-schedule_application",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> updateTestJobSchedule(#RequestBody
TestJobScheduler testJobScheduler) throws URISyntaxException {
if(testJobScheduler.getIsActive() == 0){
testJobScheduler.setIsActive(1);
} else{
testJobScheduler.setIsActive(0);
}
testJobSchedulerRepository.save(testJobScheduler);
return new ResponseEntity<>("{\"status\":\"success\"}", HttpStatus.OK);
}
}
Your best bet is to use the TaskScheduler object. The taskScheduler bean is automatically created when you have #EnableScheduling on a class annotated with #Configuration. This object can be used to schedule job on the fly. Here a quick exemple of what you can do with it :
#Configuration
#EnableScheduling
public class CronJobConfiguration implements CommandLineRunner {
#Autowired
private TestJobSchedulerRepository testJobSchedulerRepository;
#Autowired
private TaskScheduler taskScheduler;
#Override
public void run(String... strings) {
String cronExpression = testJobSchedulerRepository.findByIsActive(1).getCronExpression();
Trigger trigger = new CronTrigger(cronExpression);
ScheduledFuture scheduledFuture = taskScheduler
.schedule(() -> {/* the job you want to run*/}, trigger);
/* Keep the scheduledFuture to be able to cancel the job later Ex : */
scheduledFuture.cancel(true);
}
}
You'll probably need to keep the ScheduledFuture object returned by the schedule method to be able to cancel the job at some point. Also you'll need to have a service that use the TaskScheduler so that you can schedule job when the updateTestJobSchedule is called.

Why my scheduled jobs are not executed parallelly

I'm trying to figure out why my scheduled jobs are not executed parallelly. Maybe there is something wrong with my transaction management? Method JobScheduledExecutionService.execute() is #Scheduled with fixedRate=250, so it should be fired every 250ms no matter if previous job is finished. Due to logs it is not working as expected.
Logs: https://pastebin.com/M6FaXpeE
My code is below.
#Service
#Slf4j
public class JobExecutionService {
private final TransactionalJobExecutionService transactionalJobExecutionService;
#Autowired
public JobExecutionService(TransactionalJobExecutionService transactionalJobExecutionService) {
this.transactionalJobExecutionService = transactionalJobExecutionService;
}
public void execute() {
TestJob job = transactionalJobExecutionService.getJob();
executeJob(job);
transactionalJobExecutionService.finishJob(job);
}
private void executeJob(TestJob testJob) {
log.debug("Execution-0: {}", testJob.toString());
Random random = new Random();
try {
Thread.sleep(random.nextInt(3000) + 200);
} catch (InterruptedException e) {
log.error("Error", e);
}
log.debug("Execution-1: {}", testJob.toString());
}
}
#Service
#Slf4j
public class JobScheduledExecutionService {
private final JobExecutionService jobExecutionService;
#Autowired
public JobScheduledExecutionService(JobExecutionService jobExecutionService) {
this.jobExecutionService = jobExecutionService;
}
#Scheduled(fixedRate = 250)
public void execute() {
log.trace("Job fired");
jobExecutionService.execute();
}
}
#Service
#Slf4j
#Transactional
public class TransactionalJobExecutionService {
private final Environment environment;
private final TestJobRepository testJobRepository;
private final TestJobResultRepository testJobResultRepository;
#Autowired
public TransactionalJobExecutionService(Environment environment, TestJobRepository testJobRepository, TestJobResultRepository testJobResultRepository) {
this.environment = environment;
this.testJobRepository = testJobRepository;
this.testJobResultRepository = testJobResultRepository;
}
public TestJob getJob() {
TestJob testJob = testJobRepository.findFirstByStatusOrderByIdAsc(
0
);
testJob.setStatus(1);
testJobRepository.save(testJob);
return testJob;
}
public void finishJob(TestJob testJob) {
testJobResultRepository.save(
new TestJobResult(
null,
testJob.getId(),
environment.getProperty("local.server.port")
)
);
}
}
#Configuration
public class SchedulingConfigurerConfiguration implements SchedulingConfigurer {
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(32);
taskScheduler.initialize();
taskRegistrar.setTaskScheduler(taskScheduler);
}
}
The reason is scheduler will fire only one event, which will be executed by one thread and then I don't see you are spawning multiple threads in your logic for parallel execution. That call of jobExecutionService.execute(); in execute() of JobScheduledExecutionService is in that one thread. So overall it ends up being sequential execution.
Seems you need to put multi-threaded [Callable-Future based] logic in JobExecutionService : execute() to pick job [transactionalJobExecutionService.getJob()] and call executeJob() inside it. hope this helps..

How to get the user that execute an asynchronous method?

I have an application that runs many asynchronous process that can take ten or twenty minutes.
On error, I use a AsyncUncaughtExceptionHandler that only writes a log error. I need to notify the user that something wrong happened.
Now I am using a "DelegatingSecurityContextAsyncTaskExecutor" which shares the spring security user between threads. But when I logout, it is deleted and the user in the context null.
This not work fine for me, I want the user who ran the method, not the current in the context,I need him to send a message to your personal mailbox.
How I can get the user that execute the method?
My code:
#Configuration
#EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {
#Override
#Bean(name = "asyncExecutor")
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(1);
executor.setThreadGroupName("MyCustomExecutor");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setBeanName("asyncExecutor");
executor.initialize();
DelegatingSecurityContextAsyncTaskExecutor securityExecutor = new DelegatingSecurityContextAsyncTaskExecutor(executor);
return securityExecutor;
}
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new CustomAsyncExceptionHandler();
}
}
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
private static final Logger LOG = LogManager.getLogger(CustomAsyncExceptionHandler.class);
#Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
LOG.error(
String.format("Error in async method %s with params: %s: Excepcion: %s",
method.getName(), params.toString(), ExceptionUtils.getStackTrace(ex)), ex);
}
}

Categories

Resources