|
@@ -0,0 +1,99 @@
|
|
|
+package org.cryptomator.common;
|
|
|
+
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+
|
|
|
+import javafx.application.Platform;
|
|
|
+import javafx.concurrent.Task;
|
|
|
+import java.util.Objects;
|
|
|
+import java.util.concurrent.BlockingQueue;
|
|
|
+import java.util.concurrent.CancellationException;
|
|
|
+import java.util.concurrent.ExecutionException;
|
|
|
+import java.util.concurrent.Future;
|
|
|
+import java.util.concurrent.ScheduledThreadPoolExecutor;
|
|
|
+import java.util.concurrent.ThreadFactory;
|
|
|
+import java.util.concurrent.ThreadPoolExecutor;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+//Inspired by: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/ThreadPoolExecutor.html#afterExecute(java.lang.Runnable,java.lang.Throwable)
|
|
|
+public final class CatchingExecutors {
|
|
|
+
|
|
|
+ private static final Logger LOG = LoggerFactory.getLogger(CatchingExecutors.class);
|
|
|
+
|
|
|
+ private CatchingExecutors() { /* NO-OP */ }
|
|
|
+
|
|
|
+ public static class CatchingScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor {
|
|
|
+
|
|
|
+ public CatchingScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) {
|
|
|
+ super(corePoolSize, threadFactory);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void afterExecute(Runnable runnable, Throwable throwable) {
|
|
|
+ super.afterExecute(runnable, throwable);
|
|
|
+ afterExecute0(runnable, throwable);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public static class CatchingThreadPoolExecutor extends ThreadPoolExecutor {
|
|
|
+
|
|
|
+ public CatchingThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
|
|
|
+ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void afterExecute(Runnable runnable, Throwable throwable) {
|
|
|
+ super.afterExecute(runnable, throwable);
|
|
|
+ afterExecute0(runnable, throwable);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void afterExecute0(Runnable runnable, Throwable throwable) {
|
|
|
+ if (throwable == null) {
|
|
|
+ if (runnable instanceof Task<?>) {
|
|
|
+ afterExecuteTask(Thread.currentThread(), (Task<?>) runnable);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ throwable = getThrowable(runnable);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (throwable != null) {
|
|
|
+ callHandler(Thread.currentThread(), throwable);
|
|
|
+ }
|
|
|
+ //Errors in this method are delegated to the UncaughtExceptionHandler of the current thread
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void callHandler(Thread thread, Throwable throwable) {
|
|
|
+ Objects.requireNonNullElseGet(thread.getUncaughtExceptionHandler(), CatchingExecutors::fallbackHandler).uncaughtException(thread, throwable);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Thread.UncaughtExceptionHandler fallbackHandler() {
|
|
|
+ return (thread, throwable) -> LOG.error("FALLBACK: Uncaught exception in " + thread.getName(), throwable);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void afterExecuteTask(Thread caller, Task<?> task) {
|
|
|
+ Platform.runLater(() -> {
|
|
|
+ if (task.getOnFailed() == null) {
|
|
|
+ callHandler(caller, task.getException());
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Throwable getThrowable(Runnable runnable) {
|
|
|
+ assert !(runnable instanceof Task<?>);
|
|
|
+
|
|
|
+ if (runnable instanceof Future<?> && ((Future<?>) runnable).isDone()) {
|
|
|
+ try {
|
|
|
+ ((Future<?>) runnable).get();
|
|
|
+ } catch (CancellationException ce) {
|
|
|
+ return ce;
|
|
|
+ } catch (ExecutionException ee) {
|
|
|
+ return ee.getCause();
|
|
|
+ } catch (InterruptedException ie) {
|
|
|
+ //Ignore/Reset
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+}
|