How do I get an exception from a canceled + interrupted call transferred to a performer?

Consider the following code:

ExecutorService executor = Executors.newSingleThreadExecutor();
final CountDownLatch taskStarted = new CountDownLatch(1);
Future<String> future = executor.submit( new Callable<String>() {
    @Override
    public synchronized String call() throws Exception {
        try {
            taskStarted.countDown();
            this.wait( 60000 );
            return "foo";                   
        }
        catch( Exception iWantToGetThisExceptionOutside ) {
            iWantToGetThisExceptionOutside.printStackTrace();
            throw iWantToGetThisExceptionOutside;
        }
    }
});
assertTrue(taskStarted.await(60, TimeUnit.SECONDS));
try {
    future.get(500, TimeUnit.MILLISECONDS);
    fail("Timeout expected.");
} catch (ExecutionException | TimeoutException e) {
    e.printStackTrace();
}
future.cancel(true); //mayInterruptIfRunning
//how to get iWantToGetThisExceptionOutside here?

      

Is there a way to get iWantToGetThisExceptionOutside

in the main thread after canceling? Do I need to create my own performer?

EDIT : Just to make it clear what is ExecutionException

chosen, but TimeoutException

that does not contain any reason. iWantToGetThisExceptionOutside

is normal InterruptedException

.

EDIT2 : A bit of clarification: The task is relatively simple. I want to be able to cancel a task if it takes too long. For this I need a get call

with a timeout that throws an exception on timeout. However, I would welcome a stack entry in my log that shows WHERE the task has been canceled. For this I will need this exception out Callable

.

+3


source to share


4 answers


You can help yourself with a customizable FutureTask

:

public class TracingFutureTask<T> extends FutureTask<T> {
    private Throwable trace;
    private boolean done;

    public TracingFutureTask(Callable<T> callable) {
        super(callable);
    }
    public TracingFutureTask(Runnable runnable, T result) {
        super(runnable, result);
    }

    @Override
    public void run() {
        try { super.run(); }
        finally { synchronized(this) { done=true; notifyAll(); }}
    }

    @Override
    protected void setException(Throwable t) {
        trace=t;
        super.setException(t);
    }
    public synchronized Throwable getException() throws InterruptedException {
        while(!done) wait();
        return trace;
    }

    public synchronized Throwable getException(long timeout)
                        throws InterruptedException, TimeoutException {

        for(long deadline = System.currentTimeMillis()+timeout, toWait=timeOut;
            !done; toWait = deadline-System.currentTimeMillis()) {

            if ( toWait <=0 ) throw new TimeoutException(
                              "Thread did not end in " + timeout + " milliseconds!" );
            wait(toWait);
        }
        return trace;
    }

    public static <V> TracingFutureTask<V> submit(Executor e, Callable<V> c) {
        TracingFutureTask<V> ft=new TracingFutureTask<>(c);
        e.execute(ft);
        return ft;
    }
    public static <V> TracingFutureTask<V> submit(Executor e, Runnable r, V v) {
        TracingFutureTask<V> ft=new TracingFutureTask<>(r, v);
        e.execute(ft);
        return ft;
    }
}

      

This keeps track of the exception in addition to the base class, but unlike the base class, it will remember this even when the job was canceled. This is why there is additional synchronization between the method run()

and getException()

, since in case of cancellation, the job may enter the canceled state (which means "done") before the exception was recorded, so we must submit our own done

with correct synchronization.

It can be used like:



ExecutorService executor = Executors.newSingleThreadExecutor();
TracingFutureTask<String> future=TracingFutureTask.submit(executor, new Callable<String>(){
    @Override
    public synchronized String call() throws Exception {
        this.wait( 60000 );
        return "foo";                   
    }
});
try {
    future.get(500, TimeUnit.MILLISECONDS);
    fail("Timeout expected.");
} catch (ExecutionException | TimeoutException e) {
    e.printStackTrace();
}
if(future.cancel(true)) {
    System.err.println("cancelled.");
    Throwable t = future.getException();
    if(t!=null) t.printStackTrace(System.err.append("cancellation caused "));
}

      

(derived from your example code)

java.util.concurrent.TimeoutException
    at java.util.concurrent.FutureTask.get(FutureTask.java:205)
    at so.TestCancel.main(TestCancel.java:69)
cancelled.
cancellation caused java.lang.InterruptedException
    at java.lang.Object.wait(Native Method)
    at so.TestCancel$1.call(TestCancel.java:64)
    at so.TestCancel$1.call(TestCancel.java:61)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at so.TracingFutureTask.run(TestCancel.java:33)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

      

+2


source


You ExecutionException

have to cheat yours at startup iWant...

Exception

.

You can add custom logic by checking Throwable

yours ExecutionException

in your expression catch

on the main thread:

// pseudo-code
if (e.getCause().[something, i.e. getMessage]) {
    // TODO something
}

      

Note

In your implementation, call

you're catch

ing and re throw

with the same Exception

, which doesn't make sense.

Note II



It doesn't really make sense to infer the timeout from the logic being called, although you can always programmatically calculate the time it takes to execute something from start call

to finish call

.

The whole timeout point is TimeoutException

meant for the caller to think that this pending task took too long.

To do this, you are catching TimeoutException

in your expression catch

.

If you need to "decorate" yours TimeoutException

, but for a specific reason causing the execution to take too long, you can:

  • programmatically calculate the time from the start of the call call

    to the end of the call call

    and a throw

    custom Exception

    one that will be wrapped ExecutionException

    (very ugly), or
  • Make your own lazy execution of each "subtask" in the method call

    and throw

    custom Exception

    for any time
+2


source


The Future.get () method is the only way to get the exceptions that are thrown from your call (). So just add more future.get();

after future.cancel(true)

.

+1


source


Instead of relying on throw / catch, you could carry the exception out of the Callable simply as an object (using synchronized shared state). It looks ugly, but it works.

    ExecutorService executor = Executors.newSingleThreadExecutor();
    final CountDownLatch taskStarted = new CountDownLatch(1);
    final CountDownLatch taskCompleted = new CountDownLatch(1);  // <- to sync on task completion
    final Exception[] wasSomethingWrong = new Exception[1];      // <- not thread safe, but works here  
    Future<String> future = executor.submit( new Callable<String>() {
        @Override
        public synchronized String call() throws Exception {
            try {
                taskStarted.countDown();
                this.wait( 60000 );
            }
            catch( Exception iWantToGetThisExceptionOutside ) {
                wasSomethingWrong[0] = iWantToGetThisExceptionOutside; // <-
            } finally {
                taskCompleted.countDown();  // <- 
            }
            return "foo";
        }
    });
    assertTrue(taskStarted.await(60, TimeUnit.SECONDS));
    try {
        future.get(500, TimeUnit.MILLISECONDS);
        fail("Timeout expected.");
    } catch (ExecutionException | TimeoutException e) {
        e.printStackTrace();
    }
    future.cancel(true); //mayInterruptIfRunning
    taskCompleted.await(60, TimeUnit.SECONDS);  // <- sync

    assertNotNull(wasSomethingWrong[0]);
    System.out.println(Arrays.toString(wasSomethingWrong[0].getStackTrace()));
    assertEquals(InterruptedException.class, wasSomethingWrong[0].getClass());  // <- PROFIT

      

+1


source







All Articles