Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interrupt all running tasks during shutdown #6118

Merged
merged 10 commits into from
Sep 1, 2020
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
42 changes: 37 additions & 5 deletions src/main/java/org/jabref/JabRefExecutorService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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();
}

Expand All @@ -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();
}
}
}
3 changes: 3 additions & 0 deletions src/main/java/org/jabref/gui/util/DefaultTaskExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ public <V> Future<?> schedule(BackgroundTask<V> 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();
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/jabref/gui/util/TaskExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public interface TaskExecutor {
<V> Future<?> schedule(BackgroundTask<V> 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();

Expand Down
8 changes: 7 additions & 1 deletion src/main/java/org/jabref/logic/util/DelayTaskThrottler.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
}