3

I'm working on a multithreading project that has a case where a Thread may throw an Error (not an Exception). Not finding any solid information about how Errors are handled in multithreading, I decided to do some tests and found that results can be inconsistent.

This is my test code, along with the commented results.

public class MultiThreadError {
    public static class ErrorThrowingRunnable implements Runnable{
        private final boolean throwsError;
        public ErrorThrowingRunnable(boolean throwsError){
            this.throwsError = throwsError;
        }

        @Override
        public void run() {
            try {
                // Wait between .5 and 1.5 seconds
                Thread.sleep(500 + new Random().nextInt(1000));
            } catch (InterruptedException ex) {}
            if(throwsError){
                throw new Error(Thread.currentThread().getName());
            }else{
                System.out.println(Thread.currentThread().getName());
            }
        }
    }

    public static void regularThreadPool(){
        // Crashes individual thread; swallows error
        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        threadPool.submit(new ErrorThrowingRunnable(false));
        threadPool.submit(new ErrorThrowingRunnable(false));
        threadPool.submit(new ErrorThrowingRunnable(false));
        threadPool.submit(new ErrorThrowingRunnable(false));
        threadPool.submit(new ErrorThrowingRunnable(true));
        threadPool.shutdown();
    }

    public static void onDemandThreads(){
        // Crashes individual thread; displays error
        new Thread(new ErrorThrowingRunnable(false)).start();
        new Thread(new ErrorThrowingRunnable(false)).start();
        new Thread(new ErrorThrowingRunnable(false)).start();
        new Thread(new ErrorThrowingRunnable(false)).start();
        new Thread(new ErrorThrowingRunnable(true)).start();
    }

    public static void onDemandThreadPool(){
        // Same as onDemandThreads()
        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        threadPool.execute(new ErrorThrowingRunnable(false));
        threadPool.execute(new ErrorThrowingRunnable(false));
        threadPool.execute(new ErrorThrowingRunnable(false));
        threadPool.execute(new ErrorThrowingRunnable(false));
        threadPool.execute(new ErrorThrowingRunnable(true));
        threadPool.shutdown();
    }

    public static void tooSmallThreadPool(){
        // When an error is thrown, apparently the thread that threw
        // the error is not reused, reducing the pool size
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        threadPool.execute(new ErrorThrowingRunnable(true));
        threadPool.execute(new ErrorThrowingRunnable(false));
        threadPool.execute(new ErrorThrowingRunnable(false));
        threadPool.execute(new ErrorThrowingRunnable(false));
        threadPool.execute(new ErrorThrowingRunnable(false));
        threadPool.execute(new ErrorThrowingRunnable(false));
        threadPool.execute(new ErrorThrowingRunnable(false));
        threadPool.execute(new ErrorThrowingRunnable(false));
        threadPool.shutdown();
    }
}

It seems as though the outcome is supposed to be what I expected: the thread throwing the Error terminates, displaying the message. It turns out that when a Runnable is passed to an ExecutorService using submit(Runnable), it's wrapped in a RunnableFuture<Void> that doesn't handle errors, and I can't find a way to change this behavior other than directly calling execute(Runnable), which for some reason doesn't exhibit the same behavior.

Is there a "best practice" for this? If I know a Thread may throw an Error, is there a way to submit it to an ExecutorService and not swallow the error?

ndm13
  • 1,189
  • 13
  • 19
  • On a wee style note, you should avoid throwing `Errors`. They indicate serious problems that you shouldn't try to deal with anyway (such as running out of memory). If you're looking to throw something from a `Runnable`, you should use a `RuntimeException` (or a subclass thereof) instead. – Joe C Sep 28 '16 at 22:11
  • Possible duplicate of [Handling exceptions from Java ExecutorService tasks](http://stackoverflow.com/questions/2248131/handling-exceptions-from-java-executorservice-tasks) – Ravindra babu Sep 30 '16 at 09:39
  • 1
    Just to make sure, the `RunnableFuture` _handles_ the errors just fine. All runnable calls are wrapped in a try/finally. You mean to say that you can't see the error messages I suspect. – Gray Sep 30 '16 at 17:00

3 Answers3

5

Yes, submit your task to ExecutorService and check the result in returned Future.

When used:

ExecutorService es = Executors.newFixedThreadPool(1);

Future<?> result = es.submit(new Runnable() {
    @Override
    public void run() {
        throw new Error("sample error");
    }
});

try {
    result.get();
} catch (ExecutionException e) {
    e.printStackTrace();
}

your stack trace will contain:

java.util.concurrent.ExecutionException: java.lang.Error: sample error
    at java.util.concurrent.FutureTask.report(Unknown Source)
    at java.util.concurrent.FutureTask.get(Unknown Source)
    at jjj.b.B.main(B.java:23)
Caused by: java.lang.Error: sample error
    at jjj.b.B$1.call(B.java:18)
    at jjj.b.B$1.call(B.java:1)
    at java.util.concurrent.FutureTask.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)
Gray
  • 115,027
  • 24
  • 293
  • 354
MGorgon
  • 2,547
  • 23
  • 41
  • Are you sure that an Error will be caught if it says it throws Exception? – ndm13 Sep 29 '16 at 17:33
  • 1
    @ndm13 yes, it would be: "java.util.concurrent.ExecutionException: java.lang.Error: sample error". I've updated the answer. – MGorgon Sep 29 '16 at 21:33
  • 1
    @ndm13 i've updated answer to use plain Runnable here. Error can be thrown from it without throws declaration. – MGorgon Sep 29 '16 at 21:41
  • 1
    Typically we use the `getCause()` method of the `ExecutionException` which is explained in the javadocs. – Gray Sep 30 '16 at 16:59
1

Trying to handle an Error thrown from a thread seems to be a suspicious use case...

From Java Doc : "An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch. Most such errors are abnormal conditions. The ThreadDeath error, though a "normal" condition, is also a subclass of Error because most applications should not try to catch it."

The only time I needed to handle Error is when I use third parties that badly manage their exceptions, that throw Error rather than RuntimeException. In this specific cases, you need to catch the Error (or the Throwable) to avoid unexpected application crash.

Joseph M. Dion
  • 356
  • 3
  • 7
  • So essentially, the best practice is to throw a RuntimeException and handle it like any other Exception, even if an Error would be normally thrown, if it's within a thread? – ndm13 Sep 29 '16 at 17:35
  • 1
    Personnally I don't find any reason why you should throw Error in your code. Now, if you need to use code that your are not responsible of, such as third parties, that throws Error, the question is : should I catch it or not. This is a case by case thing but I would say that you could catch the Error if its cause is recoverable. For example, let's say that you web application uses a third party such as velocity that throws an Error because of an invalid template, I believe you should catch and handle the Error because the web service can continue to run. – Joseph M. Dion Sep 30 '16 at 13:27
0

A Future is an object that represents the result of running your operation, whether it be ready now or at some point in the future. Generally, one will call the get method on said future in order to get a result, particularly if you are passing a Callable rather than a Runnable into your ExecutorService.

If your Runnable/Callable throws an exception, this will be reflected in the Future object. You will be able to test whether the Runnable ran successfully by calling the get method. If it was a clean run, you will get (in this case) null back. If an exception was thrown, then get will throw an ExecutionException, with that exception marked as its cause.

Think of it like a coat check at a music venue. If they lose your coat, they probably won't tell you until you come with your ticket asking for your coat. The Future serves the purpose of your ticket here.

Joe C
  • 15,324
  • 8
  • 38
  • 50