Small Java and SpringBoot question please.
I would like to build an endpoint, which when a client calls in, will return immediately, but after a minute from the call, will trigger a job.
I have no control over when the client triggers the requests, I cannot hold the client for that time, and I have no control over the client at all.
Basically, I would like to achieve something like this:
@GetMapping(path = "/triggerJobMinuteLaterButReturnImmediately")
public String triggerJobMinuteLaterButReturnImmediately() {
invokeThisMethodOneMinuteLater();
return "jobTriggered";
}
What I tried so far, is to come up with a code like this:
@GetMapping(path = "/triggerJobMinuteLaterButReturnImmediately")
public String triggerJobMinuteLaterButReturnImmediately() {
try {
Thread.sleep(60000);
invokeThisMethodOneMinuteLater();
} catch (InterruptedException e) {
e.printStackTrace();
}
return "jobTriggered";
}
This works in terms of triggering the job one minute later, happy, but I am not returning immediately, I am holding the client during all that time.
May I ask how to achieve the trigger of the job, minute later from the client http call, but able to return a response immediately to the client please?
Thank you
CodePudding user response:
Probably the simplest would be to start a separate thread (although if you're doing this a lot, a separate threadpool should be used, supplyAsync()
uses the common pool which is of limited size), this lets the container's worker thread return the response and finish the request.
@GetMapping(path = "/triggerJobMinuteLaterButReturnImmediately")
public String triggerJobMinuteLaterButReturnImmediately() {
CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(60000);
invokeThisMethodOneMinuteLater();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
return "jobTriggered";
}
CodePudding user response:
You definetely don't want to use Thread.sleep
. In this case, you're keeping the connection open for 60 seconds. If user closes the browser tab, it may result in error.
The better option is to apply ScheduledExecutorService
.
class AsyncConfig {
@Bean
public ScheduledExecutorService scheduledExecutorService() {
// number of threads should be tuned with a property
return Executors.newScheduledThreadPool(threadNum);
}
}
@RestController
class SomeController {
@Autowired
private ScheduledExecutorService scheduler;
@GetMapping(path = "/triggerJobMinuteLaterButReturnImmediately")
public String triggerJobMinuteLaterButReturnImmediately() {
scheduler.schedule(() -> invokeThisMethodOneMinuteLater(), 1, TimeUnit.MINUTES);
return "jobTriggered";
}
}
Now a user gets response immediately while job is being scheduled.
Nevertheless, this approach is not perfect either. The service can crash after the job has been successfully registered. So, it won't be invoked eventually. If you want to overcome this obstacle, you should store the job info in some external storage (e.g. relational database). And then check it periodically, execute it, and mark as done.