SafeShutdownRunner implements AutoCloseable { public void shutdownSafely(Runnable shutdownCallback) { Future<?> future = executor.submit(shutdownCallback); try { future.get(timeoutDuration.toMillis(), TimeUnit.MILLISECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); failures.add(e); } catch (ExecutionException e) { failures.add(e.getCause()); } catch (TimeoutException e) { future.cancel(true); failures.add(e); } } SafeShutdownRunner(Duration timeoutDuration); void shutdownSafely(Runnable shutdownCallback); @Override void close();  }
@Test public void runnerRunsOneTask() { SafeShutdownRunner runner = new SafeShutdownRunner(Duration.ofSeconds(1)); runner.shutdownSafely(mockRunnable); verify(mockRunnable, times(1)).run(); assertThatCode(runner::close).doesNotThrowAnyException(); }
@Test public void exceptionsAreSuppressedAndReportedWhenClosing() { SafeShutdownRunner runner = new SafeShutdownRunner(Duration.ofSeconds(1)); assertThatCode(() -> runner.shutdownSafely(throwingRunnable)).doesNotThrowAnyException(); assertThatThrownBy(runner::close) .isInstanceOf(SafeRuntimeException.class) .hasSuppressedException(EXCEPTION); }
@Test public void slowTasksTimeOutWithoutThrowing() { SafeShutdownRunner runner = new SafeShutdownRunner(Duration.ofMillis(50)); runner.shutdownSafely(blockingUninterruptibleRunnable); runner.shutdownSafely(blockingUninterruptibleRunnable); verify(blockingUninterruptibleRunnable, times(2)).run(); closeAndAssertNumberOfTimeouts(runner, 2); }
@Test public void otherTasksStillRunInPresenceOfSlowTasksThatTimeOut() { SafeShutdownRunner runner = new SafeShutdownRunner(Duration.ofMillis(50)); runner.shutdownSafely(blockingUninterruptibleRunnable); runner.shutdownSafely(blockingUninterruptibleRunnable); runner.shutdownSafely(mockRunnable); runner.shutdownSafely(blockingUninterruptibleRunnable); runner.shutdownSafely(blockingUninterruptibleRunnable); runner.shutdownSafely(mockRunnable); verify(blockingUninterruptibleRunnable, times(4)).run(); verify(mockRunnable, times(2)).run(); closeAndAssertNumberOfTimeouts(runner, 4); }