Как сделать ошибки отчета 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 ответа:
Пулы потоков валюты захватывают все исключения и помещают затем в будущий объект для проверки.
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). Таким образом, исполнитель не остановится из-за исключения, и вы увидите, что происходит.