I have a Resource Class
, with a @ManagedAsync
method Class which looks like this:
@Path("my-resource")
public class MyResource extends BaseResource{
private DatumDAO datumDAO;
public MyResource(DatumDAO datumDAO){
this.datumDAO = datumDAO;
}
public void cleanDatum(Datum datum){
//time taking operations
}
@GET
@ManagedAsync
@Path("/cleanup/{from}/{till}/")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@UnitOfWork
public void cleanupDirtyData(@Suspended final AsyncResponse asyncResponse, @PathParam("from") DateTimeParam from,
@PathParam("till") DateTimeParam till) throws IOException{
logger.debug("will try to cleanup dirty data in range: " + from + " " + till);
List<Datum> data = datumDAO.getALlDirtyDatumInRange(from.get().toDate(), till.get().toDate());
Map<Long,String> cleanupMap = new HashMap<Long,String>();
for(Datum datum: data){
cleanDatum(datum);
cleanupMap.put(datum.getId(), "cleaned");
}
// this response need to be sent [can be ignored]
asyncResponse.resume(Response.status(HttpStatus.OK_200).entity(cleanupMap).build());
}
}
Since the call to cleanupDirtyData
can take a while, I don't want clients to wait for it completely, I understand that execution work is offloaded to a different worker thread.
What I am trying to achieve is to give an immediate response to the client and keep doing execution of the function cleanupDirtyData
asynchronously.
So tried the following thing:
Putting a forceful timeout, and giving a premature response to the client, but that doesn't seem to be the ideal way and it stops the execution.
that would look something like this:
@Path("my-resource")
public class MyResource extends BaseResource{
private DatumDAO datumDAO;
public MyResource(DatumDAO datumDAO){
this.datumDAO = datumDAO;
}
public void cleanDatum(Datum datum){
//time taking operations
}
@GET
@ManagedAsync
@Path("/cleanup/{from}/{till}/")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@UnitOfWork
public void cleanupDirtyData(@Suspended final AsyncResponse asyncResponse, @PathParam("from") DateTimeParam from,
@PathParam("till") DateTimeParam till) throws IOException{
// Register handler and set timeout
asyncResponse.setTimeoutHandler(new TimeoutHandler() {
public void handleTimeout(AsyncResponse ar) {
asyncResponse.resume(Response.status(SERVICE_UNAVAILABLE).entity(
"Operation timed out -- please try again").build());
}
});
ar.setTimeout(15, TimeUnit.SECONDS);
logger.debug("will try to cleanup dirty data in range: " + from + " " + till);
List<Datum> data = datumDAO.getALlDirtyDatumInRange(from.get().toDate(), till.get().toDate());
Map<Long,String> cleanupMap = new HashMap<Long,String>();
for(Datum datum: data){
cleanDatum(datum);
cleanupMap.put(datum.getId(), "cleaned");
}
// this response need to be sent [can be ignored]
asyncResponse.resume(Response.status(HttpStatus.OK_200).entity(cleanupMap).build());
}
}
The JAX-RS Asynchronous Server API is all about how the container will manage the request. But it will still hold the request and won't affect the client experience.
Quoting the Jersey documentation about the Asynchronous Server API:
Note that the use of server-side asynchronous processing model will not improve the request processing time perceived by the client. It will however increase the throughput of the server, by releasing the initial request processing thread back to the I/O container while the request may still be waiting in a queue for processing or the processing may still be running on another dedicated thread. The released I/O container thread can be used to accept and process new incoming request connections.
If you want to give the client an immediate response, you may be looking for something like:
@Singleton
@Path("expensive-task")
public class ExpensiveTaskResource {
private ExecutorService executor;
private Future<String> futureResult;
@PostConstruct
public void onCreate() {
this.executor = Executors.newSingleThreadExecutor();
}
@POST
public Response startTask() {
futureResult = executor.submit(new ExpensiveTask());
return Response.status(Status.ACCEPTED).build();
}
@GET
public Response getResult() throws ExecutionException, InterruptedException {
if (futureResult != null && futureResult.isDone()) {
return Response.status(Status.OK).entity(futureResult.get()).build();
} else {
return Response.status(Status.FORBIDDEN).entity("Try later").build();
}
}
@PreDestroy
public void onDestroy() {
this.executor.shutdownNow();
}
}
public class ExpensiveTask implements Callable<String> {
@Override
public String call() throws Exception {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Task completed";
}
}
In a servlet container, you can use an ExecutorService
to run your expensive task. In a Java EE container, you should consider a ManagedExecutorService
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With