11

I want to handle exeptions thrown by worker threads in ThreadPoolExecutor#afterExecute() method. Currently I have this code:

public class MyExecutor extends ThreadPoolExecutor {

    public static void main(String[] args) {
        MyExecutor threadPool = new MyExecutor();
        Task<Object> task = new Task<>();
        threadPool.submit(task);
    }

    public MyExecutor() {
        super(4, 20, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(4000));
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        System.out.println("in afterExecute()");
        if (t != null) {
            System.out.println("exception thrown: " + t.getMessage());
        } else {
            System.out.println("t == null");
        }
    }

    private static class Task<V> implements Callable<V> {

        @Override
        public V call() throws Exception {
            System.out.println("in call()");
            throw new SQLException("testing..");
        }
    }
}

If I run the code I get output:

in call()
in afterExecute()
t == null

Why is parameter Throwable t null in afterExecute()? Shouldn't it be the SQLException instance?

Tunaki
  • 132,869
  • 46
  • 340
  • 423
Kaarel Purde
  • 1,255
  • 4
  • 18
  • 38

2 Answers2

11

This is actually expected behaviour.

Quoting afterExecute Javadoc:

If non-null, the Throwable is the uncaught RuntimeException or Error that caused execution to terminate abruptly.

This means the throwable instance will be RuntimeException or Error, not checked Exception. Since SQLException is a checked exception, it won't be passed to afterExecute.

There is also something else going on here (still quoting the Javadoc):

Note: When actions are enclosed in tasks (such as FutureTask) either explicitly or via methods such as submit, these task objects catch and maintain computational exceptions, and so they do not cause abrupt termination, and the internal exceptions are not passed to this method.

In your example, the task is enclosed in a FutureTask since you are submitting a Callable, so you are in this case. Even in you change your code to throw a RuntimeException, if won't be given to afterExecute. The Javadoc gives a sample code to deal with this, which I'm copying here, for reference:

protected void afterExecute(Runnable r, Throwable t) {
     super.afterExecute(r, t);
     if (t == null && r instanceof Future) {
       try {
         Object result = ((Future) r).get();
       } catch (CancellationException ce) {
           t = ce;
       } catch (ExecutionException ee) {
           t = ee.getCause();
       } catch (InterruptedException ie) {
           Thread.currentThread().interrupt(); // ignore/reset
       }
     }
     if (t != null)
       System.out.println(t);
}
Tunaki
  • 132,869
  • 46
  • 340
  • 423
  • Thanks for clearing that up. So all checked exceptions are just going to be swallowed by the ThreadPoolExecutor? And all exception handling must be done in Callable#call()? – Kaarel Purde Nov 17 '15 at 14:05
  • 1
    @potato300 See my edit, there is something else going on in your specific example (that I didn't notice at first) – Tunaki Nov 17 '15 at 14:12
2

This is an alternate way of doing it. Taking hint from here

package com.autonomy.introspect.service;

import java.sql.SQLException;
import java.util.concurrent.*;

public class MyExecutor extends ThreadPoolExecutor {

    public static void main(String[] args) {
        MyExecutor threadPool = new MyExecutor();
        Task<Object> task = new Task<Object>();
        Future<Object> futureTask = threadPool.submit(task);
        try {
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            System.out.println("exception thrown: " + e.getMessage());
        }
    }

    public MyExecutor() {
        super(4, 20, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(4000));
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        System.out.println("in afterExecute()");
        if (t != null) {
            System.out.println("exception thrown: " + t.getMessage());
        } else {
            System.out.println("t == null");
        }
    }

    private static class Task<V> implements Callable<V> {

        @Override
        public V call() throws Exception {
            System.out.println("in call()");
            throw new SQLException("testing..");
        }
    }
}

The usage of afterExecute is for a different purpose.

This class provides protected overridable beforeExecute(java.lang.Thread,
java.lang.Runnable) and afterExecute(java.lang.Runnable, 
java.lang.Throwable) methods that are called before and after execution of 
each task. These can be used to manipulate the execution environment; for 
example, reinitializing ThreadLocals, gathering statistics, or adding log 
entries. Additionally, method terminated() can be overridden to perform any 
special processing that needs to be done once the Executor has fully 
terminated.

If hook or callback methods throw exceptions, internal worker threads may 

in turn fail and abruptly terminate.

Community
  • 1
  • 1
Tyagi Akhilesh
  • 744
  • 6
  • 15