diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b56837dd03..a2c3d40e931 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve ### Fixed +- We fixed an issue where the `.sav` file was not deleted upon exiting JabRef. [#6109](https://github.com/JabRef/jabref/issues/6109) - We fixed a linked identifier icon inconsistency. [#6705](https://github.com/JabRef/jabref/issues/6705) - We fixed the wrong behavior that font size changes are not reflected in dialogs. [#6039](https://github.com/JabRef/jabref/issues/6039) diff --git a/src/main/java/org/jabref/JabRefExecutorService.java b/src/main/java/org/jabref/JabRefExecutorService.java index a90668d9bd2..0820bf196bc 100644 --- a/src/main/java/org/jabref/JabRefExecutorService.java +++ b/src/main/java/org/jabref/JabRefExecutorService.java @@ -22,24 +22,29 @@ public class JabRefExecutorService { public static final JabRefExecutorService INSTANCE = new JabRefExecutorService(); + private static final Logger LOGGER = LoggerFactory.getLogger(JabRefExecutorService.class); + private final ExecutorService executorService = Executors.newCachedThreadPool(r -> { Thread thread = new Thread(r); thread.setName("JabRef CachedThreadPool"); thread.setUncaughtExceptionHandler(new FallbackExceptionHandler()); return thread; }); + private final ExecutorService lowPriorityExecutorService = Executors.newCachedThreadPool(r -> { Thread thread = new Thread(r); thread.setName("JabRef LowPriorityCachedThreadPool"); thread.setUncaughtExceptionHandler(new FallbackExceptionHandler()); return thread; }); + private final Timer timer = new Timer("timer", true); + private Thread remoteThread; private JabRefExecutorService() { - } + } public void execute(Runnable command) { Objects.requireNonNull(command); @@ -132,13 +137,16 @@ public void submit(TimerTask timerTask, long millisecondsDelay) { timer.schedule(timerTask, millisecondsDelay); } + /** + * Shuts everything down. After termination, this method returns. + */ public void shutdownEverything() { - // those threads will be allowed to finish - this.executorService.shutdown(); - // those threads will be interrupted in their current task - this.lowPriorityExecutorService.shutdownNow(); // kill the remote thread stopRemoteThread(); + + gracefullyShutdown(this.executorService); + gracefullyShutdown(this.lowPriorityExecutorService); + timer.cancel(); } @@ -164,4 +172,28 @@ public void run() { } } } + + /** + * Shuts down the provided executor service by first trying a normal shutdown, then waiting for the shutdown and then forcibly shutting it down. + * Returns if the status of the shut down is known. + */ + public static void gracefullyShutdown(ExecutorService executorService) { + try { + // This is non-blocking. See https://stackoverflow.com/a/57383461/873282. + executorService.shutdown(); + if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) { + LOGGER.debug("One minute passed, {} still not completed. Trying forced shutdown.", executorService.toString()); + // those threads will be interrupted in their current task + executorService.shutdownNow(); + if (executorService.awaitTermination(60, TimeUnit.SECONDS)) { + LOGGER.debug("One minute passed again - forced shutdown of {} worked.", executorService.toString()); + } else { + LOGGER.error("{} did not terminate", executorService.toString()); + } + } + } catch (InterruptedException ie) { + executorService.shutdownNow(); + Thread.currentThread().interrupt(); + } + } } diff --git a/src/main/java/org/jabref/gui/util/DefaultTaskExecutor.java b/src/main/java/org/jabref/gui/util/DefaultTaskExecutor.java index 6cbb6b27515..a6073dcad96 100644 --- a/src/main/java/org/jabref/gui/util/DefaultTaskExecutor.java +++ b/src/main/java/org/jabref/gui/util/DefaultTaskExecutor.java @@ -122,6 +122,9 @@ public Future schedule(BackgroundTask task, long delay, TimeUnit unit) return scheduledExecutor.schedule(getJavaFXTask(task), delay, unit); } + /** + * Shuts everything down. After termination, this method returns. + */ @Override public void shutdown() { executor.shutdownNow(); diff --git a/src/main/java/org/jabref/gui/util/TaskExecutor.java b/src/main/java/org/jabref/gui/util/TaskExecutor.java index f56d516eed6..e08d858a0cc 100644 --- a/src/main/java/org/jabref/gui/util/TaskExecutor.java +++ b/src/main/java/org/jabref/gui/util/TaskExecutor.java @@ -45,7 +45,7 @@ public interface TaskExecutor { Future schedule(BackgroundTask task, long delay, TimeUnit unit); /** - * Shutdown the task executor. + * Shutdown the task executor. May happen in the background or may be finished when this method returns. */ void shutdown(); diff --git a/src/main/java/org/jabref/logic/util/DelayTaskThrottler.java b/src/main/java/org/jabref/logic/util/DelayTaskThrottler.java index 3b30027f5c3..fcb0c74551f 100644 --- a/src/main/java/org/jabref/logic/util/DelayTaskThrottler.java +++ b/src/main/java/org/jabref/logic/util/DelayTaskThrottler.java @@ -5,6 +5,8 @@ import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import org.jabref.JabRefExecutorService; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,6 +34,7 @@ public DelayTaskThrottler(int delay) { this.delay = delay; this.executor = new ScheduledThreadPoolExecutor(1); this.executor.setRemoveOnCancelPolicy(true); + this.executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); } public void schedule(Runnable command) { @@ -45,7 +48,10 @@ public void schedule(Runnable command) { } } + /** + * Shuts everything down. Upon termination, this method returns. + */ public void shutdown() { - executor.shutdown(); + JabRefExecutorService.gracefullyShutdown(executor); } }