4

I have a spring MVC app where a user can kick off a Report generation via button click. This process could take few minutes ~ 10-20 mins. I use springs @Async annotation around the service call so that report generation happens asynchronously. While I pop a message to user indicating job is currently running. Now What I want to do is, if another user (Admin) can kick off Report generation via the button which should cancel/stop currently running @Async task and restart the new task. To do this, I call the

.. ..
future = getCurrentTask(id); // returns the current task for given report id
if (!future.isDone())
    future.cancel(true);

service.generateReport(id);

How can make it so that "service.generateReport" waits while the future cancel task kills all the running threads? According to the documentation, after i call future.cancel(true), isDone will return true as well as isCancelled will return true. So there is no way of knowing the job is actually cancelled.

I can only start new report generation when old one is cancelled or completed so that it would not dirty data.

bond
  • 11,236
  • 7
  • 48
  • 62

2 Answers2

2

From documentation about cancel() method,

Subsequent calls to isCancelled() will always return true if this method returned true

Try this.

future = getCurrentTask(id); // returns the current task for given report id
if (!future.isDone()){
    boolean terminatedImmediately=future.cancel(true);
    if(terminatedImmediately)
        service.generateReport(id);
    else
        //Inform user existing job couldn't be stopped.And to try again  later
}
Ahamed Mustafa M
  • 3,069
  • 1
  • 24
  • 34
  • Documentation is one thing, real behavior is different. cancel(boolean) returns true even when the task is still being executed. – pkalinow Jul 31 '15 at 13:47
0

Assuming the code above runs in thread A, and your recently cancelled report is running in thread B, then you need thread A to stop before service.generateReport(id) and wait until thread B is completes / cancelled.

One approach to achieve this is to use Semaphore. Assuming there can be only 1 report running concurrently, first create a semaphore object acccessible by all threads (normally on the report runner service class)

Semaphore semaphore = new Semaphore(1);

At any point on your code where you need to run the report, call the acquire() method. This method will block until a permit is available. Similarly when the report execution is finished / cancelled, make sure release() is called. Release method will put the permit back and wakes up other waiting thread.

semaphore.acquire();

// run report..

semaphore.release();
gerrytan
  • 40,313
  • 9
  • 84
  • 99
  • Thanks @gerrytan, only one report can be run for given report id. We can run different report generator for different id's though. Report id 100 and 200 could run concurrently as they will not collide. But only 1 instance of report id 100 can be run. – bond Jan 13 '14 at 16:39
  • Hmm I think a more appropriate solution is to have a collection of lock object per report id and protect your report launcher code with a synchronized block against this report-specific object – gerrytan Jan 13 '14 at 22:43