Как сделать ошибки отчета ScheduledThreadPool?


После болезненного опыта отладки я отследил эту проблему: ScheduledThreadPool не сообщает о сбое задачи и не выполняет задачу, которая потерпела неудачу еще раз. Поэтому трудно следить за живучестью периодических заданий, не проверяя их с помощью других периодических заданий (через переключатель мертвеца или ScheduledFuture).

Теперь мы можем передать ScheduledThreadPool an UncaughtExceptionHandler, но даже это, кажется, не работает:

import java.util.concurrent.*;

class Test {
  public static void main(String[] args) {
    final ThreadFactory tf = new ThreadFactory() {
      private final ThreadFactory delegate = Executors.defaultThreadFactory();

      @Override public Thread newThread(final Runnable r) {
        final Thread res = delegate.newThread(r);
        res.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
          @Override
          public void uncaughtException(final Thread t, final Throwable e) {
            e.printStackTrace();
          }
        });
        return res;
      }
    };
    final ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1, tf);

    final Runnable task = new Runnable() {
      private int c = 0;

      @Override
      public void run() {
        if ( c++ == 5 ) {
          throw new ArrayIndexOutOfBoundsException("Runtime error!");
        }

        System.out.println("Reached " + c);
      }
    };

    exec.scheduleWithFixedDelay(task, 1, 1, TimeUnit.SECONDS);
  }
}

Вывод этой программы прост (Oracle Java SE (64-битный сервер) 1.7.0_06-b24)

Reached 1
Reached 2
Reached 3
Reached 4
Reached 5

И затем он висит (по замыслу).

Я всегда могу попробовать-поймать всю задачу, но это кажется уродливым; UncaughtExceptionHandler уже должен это сделать!

Существует ли API-решение для этой проблемы? Я сделал что-то не так, или это ошибка?

3 8

3 ответа:

Пулы потоков валюты захватывают все исключения и помещают затем в будущий объект для проверки. UncaughtExceptionHandler только для исключения, которое поток не ловит и убивает поток, который в этом случае будет только для исключения, вызванного кодом пула потоков.

Простой способ обойти это-обернуть свой runnable.

public class ExceptionHandlingScheduledExecutor extends ScheduledThreadPoolExecutor {
    private final Thread.UncaughtExceptionHandler ueh;

    public ExceptionHandlingScheduledExecutor(int corePoolSize, Thread.UncaughtExceptionHandler ueh) {
        super(corePoolSize);
        this.ueh = ueh;
    }

    @Override
    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
        return super.schedule(wrap(command), delay, unit);
    }

    @Override
    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
        return super.schedule(wrap(callable), delay, unit); 
    }

    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
        return super.scheduleAtFixedRate(wrap(command), initialDelay, period, unit);
    }

    @Override
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
        return super.scheduleWithFixedDelay(wrap(command), initialDelay, delay, unit);
    }

    @Override
    public void execute(Runnable command) {
        super.execute(wrap(command));
    }

    @Override
    public Future<?> submit(Runnable task) {
        return super.submit(wrap(task));
    }

    @Override
    public <T> Future<T> submit(Runnable task, T result) {
        return super.submit(wrap(task), result);
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        return super.submit(wrap(task));
    }

    private Runnable wrap(final Runnable runnable) {
        return new Runnable() {
            @Override
            public void run() {
                try {
                    runnable.run();
                } catch (final Throwable t) {
                    ueh.uncaughtException(Thread.currentThread(), t);
                    throw t;
                }
            }
        };
    }

    private <T> Callable<T> wrap(final Callable<T> callable) {
        return new Callable<T>() {
            @Override
            public T call() throws Exception {
                try {
                    return callable.call();
                } catch (Throwable t) {
                    ueh.uncaughtException(Thread.currentThread(), t);
                    throw t;
                }
            }
        };
    }
}

Вы можете подкласс ThreadPoolExecutor сделать это прозрачно.


Вы также можете использовать кэшированный пул потоков для обработки исключение, но это уже сложнее.

Один из способов использования возвращенного Future прозрачным способом - это подкласс ScheduledThreadPoolExecutor (или любой исполнитель, если на то пошло):

class MyScheduledExecutor extends ScheduledThreadPoolExecutor {
  private final Thread.UncaughtExceptionHandler ueh;
  private final ExecutorService futureService = Executors.newCachedThreadPool();

  public MyScheduledExecutor(int corePoolSize, Thread.UncaughtExceptionHandler ueh) {
    super(corePoolSize);
    this.ueh = ueh;
  }

  // Copy other constructors

  @Override
  public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                   long initialDelay,
                                                   long delay,
                                                   TimeUnit unit) {
    final ScheduledFuture<?> f = super.scheduleWithFixedDelay(command, initialDelay, delay, unit);
    futureService.submit(new Runnable() {
      @Override
      public void run() {
        try {
          f.get();
        } catch (Throwable t ) {
          ueh.uncaughtException(null, t.getCause());
        }
      }
    };

    return f;
  }

  // Do similarly for other submit/schedule methods
}

И использовать его так:

final ScheduledThreadPoolExecutor exec = new MyScheduledExecutor(1, new Thread.UncaughtExceptionHandler() {
      @Override
      public void uncaughtException(final Thread t, final Throwable e) {
        e.printStackTrace();
      }
    });

Теперь выходные данные выглядят следующим образом:

Reached 1
Reached 2
Reached 3
Reached 4
Reached 5
java.lang.ArrayIndexOutOfBoundsException: Runtime error!
   ...

Вы можете подкласс ScheduledThreadPoolExecutor и переопределить afterExecute(Runnable,Throwable) метод. вы можете обрабатывать необработанные исключения там, как вы считаете нужным.

Как правило, аналогично тому, что описал выше @PeterLawrey, любые задачи, которые я ожидаю выполнить в фоновом режиме (где я не буду отслеживать будущее), я оберну блоком try{}catch(Throwable){} и обработаю исключения, которые избегают самого рабочего кода.

Вы можете использовать VerboseRunnable класс из jcabi-log, который выполняет обертывание, предложенное выше:

import com.jcabi.log.VerboseRunnable;
Runnable runnable = new VerboseRunnable(
  Runnable() {
    public void run() { 
      // do business logic, may Exception occurs
    }
  },
  true // it means that all exceptions will be swallowed and logged
);

Теперь, когда executor вызывает runnable.run(), исключения не создаются. Вместо этого они проглатываются и регистрируются (в SLF4J). Таким образом, исполнитель не остановится из-за исключения, и вы увидите, что происходит.